lcpfs 2026.1.102

LCP File System - A ZFS-inspired copy-on-write filesystem for Rust
// Copyright 2025 LunaOS Contributors
// SPDX-License-Identifier: Apache-2.0
//
// Direct I/O
// Bypass ARC cache for specific workloads.

/// Direct I/O flags for cache bypass
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DirectIoFlags {
    /// Skip read caching (don't populate ARC)
    pub skip_read_cache: bool,
    /// Skip write caching (write-through to disk)
    pub skip_write_cache: bool,
    /// Use O_DIRECT semantics (aligned, unbuffered)
    pub unbuffered: bool,
}

impl DirectIoFlags {
    /// Create new Direct I/O flags with all features enabled
    pub fn enabled() -> Self {
        Self {
            skip_read_cache: true,
            skip_write_cache: true,
            unbuffered: true,
        }
    }

    /// Create flags for read-only Direct I/O
    pub fn read_only() -> Self {
        Self {
            skip_read_cache: true,
            skip_write_cache: false,
            unbuffered: true,
        }
    }

    /// Create flags for write-only Direct I/O
    pub fn write_only() -> Self {
        Self {
            skip_read_cache: false,
            skip_write_cache: true,
            unbuffered: true,
        }
    }

    /// Create flags with all features disabled (normal cached I/O)
    pub fn disabled() -> Self {
        Self {
            skip_read_cache: false,
            skip_write_cache: false,
            unbuffered: false,
        }
    }

    /// Check if any Direct I/O feature is enabled
    pub fn is_enabled(&self) -> bool {
        self.skip_read_cache || self.skip_write_cache || self.unbuffered
    }
}

/// Direct I/O engine
pub struct DirectIoEngine;

impl DirectIoEngine {
    /// Check if Direct I/O should be used for this operation
    ///
    /// # Heuristics
    /// - Sequential reads > 1 MB: Use Direct I/O
    /// - Sequential writes > 1 MB: Use Direct I/O
    /// - Random access: Use ARC (cached I/O)
    /// - Small I/O < 64 KB: Use ARC
    ///
    /// # Arguments
    /// * `offset` - File offset
    /// * `size` - I/O size in bytes
    /// * `last_offset` - Previous I/O offset (for sequential detection)
    ///
    /// # Returns
    /// * `true` - Use Direct I/O
    /// * `false` - Use cached I/O (ARC)
    pub fn should_use_direct_io(offset: u64, size: u64, last_offset: Option<u64>) -> bool {
        const DIRECT_IO_THRESHOLD: u64 = 1024 * 1024; // 1 MB
        const SMALL_IO_THRESHOLD: u64 = 64 * 1024; // 64 KB
        const SEQUENTIAL_WINDOW: u64 = 128 * 1024; // 128 KB window

        // Small I/O always uses cache
        if size < SMALL_IO_THRESHOLD {
            return false;
        }

        // Large I/O: check if sequential
        if size >= DIRECT_IO_THRESHOLD {
            if let Some(last) = last_offset {
                // Sequential if current offset is within window of last offset
                let is_sequential = offset.abs_diff(last) <= SEQUENTIAL_WINDOW;
                return is_sequential;
            }
            // First large I/O: assume sequential
            return true;
        }

        // Medium I/O: use cache
        false
    }

    /// Validate alignment for Direct I/O
    ///
    /// Direct I/O requires block-aligned buffers and sizes
    ///
    /// # Arguments
    /// * `offset` - File offset (must be block-aligned)
    /// * `size` - I/O size (must be multiple of block size)
    /// * `block_size` - Block size (typically 4096)
    ///
    /// # Returns
    /// * `Ok(())` - Alignment valid
    /// * `Err(msg)` - Alignment error
    pub fn validate_alignment(offset: u64, size: u64, block_size: u64) -> Result<(), &'static str> {
        if offset % block_size != 0 {
            return Err("Offset not block-aligned");
        }

        if size % block_size != 0 {
            return Err("Size not multiple of block size");
        }

        Ok(())
    }

    /// Calculate optimal I/O size for Direct I/O
    ///
    /// # Arguments
    /// * `requested_size` - Requested I/O size
    /// * `block_size` - Block size
    ///
    /// # Returns
    /// Rounded-up size that's a multiple of block_size
    pub fn align_size(requested_size: u64, block_size: u64) -> u64 {
        requested_size.div_ceil(block_size) * block_size
    }

    /// Get recommended block size for Direct I/O
    ///
    /// # Arguments
    /// * `device_type` - Type of storage device
    ///
    /// # Returns
    /// Recommended block size in bytes
    pub fn recommended_block_size(device_type: DeviceType) -> u64 {
        match device_type {
            DeviceType::Nvme => 4096, // 4K for NVMe
            DeviceType::Ssd => 4096,  // 4K for SSD
            DeviceType::Hdd => 4096,  // 4K for HDD (sector size)
            DeviceType::Ram => 4096,  // 4K for RAM disk
        }
    }
}

/// Storage device type for Direct I/O optimization
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeviceType {
    /// NVMe SSD
    Nvme,
    /// SATA/SAS SSD
    Ssd,
    /// Hard disk drive
    Hdd,
    /// RAM disk
    Ram,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_direct_io_flags() {
        let enabled = DirectIoFlags::enabled();
        assert!(enabled.skip_read_cache);
        assert!(enabled.skip_write_cache);
        assert!(enabled.unbuffered);
        assert!(enabled.is_enabled());

        let disabled = DirectIoFlags::disabled();
        assert!(!disabled.skip_read_cache);
        assert!(!disabled.skip_write_cache);
        assert!(!disabled.unbuffered);
        assert!(!disabled.is_enabled());
    }

    #[test]
    fn test_should_use_direct_io_small() {
        // Small I/O should use cache
        assert!(!DirectIoEngine::should_use_direct_io(0, 4096, None));
        assert!(!DirectIoEngine::should_use_direct_io(0, 32 * 1024, None));
    }

    #[test]
    fn test_should_use_direct_io_large_sequential() {
        // Large sequential I/O should use Direct I/O
        let size = 2 * 1024 * 1024; // 2 MB
        let offset1 = 0;
        let offset2 = 64 * 1024; // 64 KB later (within sequential window)

        // First large I/O should use Direct I/O
        assert!(DirectIoEngine::should_use_direct_io(offset1, size, None));
        // Second I/O within sequential window should also use Direct I/O
        assert!(DirectIoEngine::should_use_direct_io(
            offset2,
            size,
            Some(offset1)
        ));
    }

    #[test]
    fn test_should_use_direct_io_large_random() {
        // Large random I/O should use cache
        let offset1 = 0;
        let offset2 = 100 * 1024 * 1024; // 100 MB later (random)
        let size = 2 * 1024 * 1024; // 2 MB

        assert!(DirectIoEngine::should_use_direct_io(offset1, size, None));
        // Random access (large gap) should not use Direct I/O
        assert!(!DirectIoEngine::should_use_direct_io(
            offset2,
            size,
            Some(offset1)
        ));
    }

    #[test]
    fn test_alignment_validation() {
        let block_size = 4096;

        // Valid alignment
        assert!(DirectIoEngine::validate_alignment(0, 4096, block_size).is_ok());
        assert!(DirectIoEngine::validate_alignment(4096, 8192, block_size).is_ok());

        // Invalid offset
        assert!(DirectIoEngine::validate_alignment(100, 4096, block_size).is_err());

        // Invalid size
        assert!(DirectIoEngine::validate_alignment(0, 4000, block_size).is_err());
    }

    #[test]
    fn test_align_size() {
        let block_size = 4096;

        assert_eq!(DirectIoEngine::align_size(4096, block_size), 4096);
        assert_eq!(DirectIoEngine::align_size(4097, block_size), 8192);
        assert_eq!(DirectIoEngine::align_size(8000, block_size), 8192);
        assert_eq!(DirectIoEngine::align_size(1, block_size), 4096);
    }

    #[test]
    fn test_recommended_block_size() {
        assert_eq!(
            DirectIoEngine::recommended_block_size(DeviceType::Nvme),
            4096
        );
        assert_eq!(
            DirectIoEngine::recommended_block_size(DeviceType::Ssd),
            4096
        );
        assert_eq!(
            DirectIoEngine::recommended_block_size(DeviceType::Hdd),
            4096
        );
        assert_eq!(
            DirectIoEngine::recommended_block_size(DeviceType::Ram),
            4096
        );
    }
}