irontide-storage 0.165.0

Piece storage, verification, and disk I/O for BitTorrent
Documentation
//! IOCP storage helpers: config and pre-opened HANDLE management.

use std::path::{Path, PathBuf};

use windows_sys::Win32::Foundation::{CloseHandle, HANDLE, INVALID_HANDLE_VALUE};
use windows_sys::Win32::Storage::FileSystem::{
    CreateFileW, FILE_FLAG_NO_BUFFERING, FILE_FLAG_OVERLAPPED, FILE_GENERIC_READ,
    FILE_GENERIC_WRITE, FILE_SHARE_READ, OPEN_ALWAYS,
};

/// Configuration for the IOCP disk I/O backend.
#[derive(Debug, Clone)]
pub struct IocpConfig {
    /// Number of concurrent I/O threads (0 = system default). Default: 0.
    pub concurrent_threads: u32,
    /// Enable `FILE_FLAG_NO_BUFFERING` for bypassing the OS page cache. Default: false.
    /// When true, writes must be sector-aligned (typically 4096 bytes).
    pub enable_direct_io: bool,
}

impl Default for IocpConfig {
    fn default() -> Self {
        Self {
            concurrent_threads: 0,
            enable_direct_io: false,
        }
    }
}

/// Pre-opened HANDLEs for IOCP overlapped I/O submission.
///
/// Holds one [`HANDLE`] per torrent file, opened with `CreateFileW` (overlapped mode).
/// The [`Drop`] implementation closes all handles.
pub struct IocpStorageState {
    handles: Vec<HANDLE>,
}

impl IocpStorageState {
    /// Open all torrent files and return their HANDLEs.
    ///
    /// Each file is opened with `FILE_FLAG_OVERLAPPED` for async I/O. When
    /// `direct_io` is true, `FILE_FLAG_NO_BUFFERING` is added.
    ///
    /// # Errors
    ///
    /// Returns an I/O error if `CreateFileW` fails. On failure, all previously
    /// opened handles are closed before returning.
    pub fn open_files(
        base_dir: &Path,
        file_paths: &[PathBuf],
        direct_io: bool,
    ) -> std::io::Result<Self> {
        let mut handles = Vec::with_capacity(file_paths.len());

        for path in file_paths {
            let full = base_dir.join(path);
            // Convert to null-terminated UTF-16 for CreateFileW.
            let wide: Vec<u16> = std::os::windows::ffi::OsStrExt::encode_wide(full.as_os_str())
                .chain(std::iter::once(0))
                .collect();

            let mut flags = FILE_FLAG_OVERLAPPED;
            if direct_io {
                flags |= FILE_FLAG_NO_BUFFERING;
            }

            // SAFETY: `wide` is a valid null-terminated UTF-16 string. The flags
            // and access modes are valid values for `CreateFileW`. The returned
            // handle is checked for `INVALID_HANDLE_VALUE` before use.
            let handle = unsafe {
                CreateFileW(
                    wide.as_ptr(),
                    FILE_GENERIC_READ | FILE_GENERIC_WRITE,
                    FILE_SHARE_READ,
                    std::ptr::null(),
                    OPEN_ALWAYS,
                    flags,
                    std::ptr::null_mut(),
                )
            };

            if handle == INVALID_HANDLE_VALUE {
                // Close any already-opened handles before returning the error.
                for &h in &handles {
                    // SAFETY: `h` was returned by a successful `CreateFileW` call
                    // earlier in this function and has not been closed yet.
                    unsafe {
                        CloseHandle(h);
                    }
                }
                return Err(std::io::Error::last_os_error());
            }
            handles.push(handle);
        }

        Ok(Self { handles })
    }

    /// Get the HANDLE for a given file index.
    ///
    /// # Panics
    ///
    /// Panics if `file_index >= self.handles.len()`.
    pub fn handle(&self, file_index: usize) -> HANDLE {
        self.handles[file_index]
    }

    /// Number of open handles.
    #[must_use]
    pub fn len(&self) -> usize {
        self.handles.len()
    }

    /// Returns true if no handles are held.
    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.handles.is_empty()
    }
}

impl Drop for IocpStorageState {
    fn drop(&mut self) {
        for &h in &self.handles {
            // SAFETY: each handle was returned by a successful `CreateFileW` call
            // in `open_files()` and has not been closed since (barring external
            // misuse of the HANDLE obtained via `handle()`).
            unsafe {
                CloseHandle(h);
            }
        }
    }
}

// SAFETY: Windows HANDLEs are process-global and can be used from any thread.
unsafe impl Send for IocpStorageState {}
unsafe impl Sync for IocpStorageState {}