file-engine 1.1.1

Async, cross-platform file operations engine for desktop apps and developer tools: copy, move, sync, watch, and compress files with progress reporting and cancellation.
Documentation
use std::iter;
use std::os::windows::ffi::OsStrExt;
use std::path::Path;

use windows_sys::Win32::Storage::FileSystem::{GetVolumeInformationW, GetVolumePathNameW};
use windows_sys::Win32::System::SystemServices::FILE_CASE_SENSITIVE_SEARCH;

use crate::error::Result;

use super::{classify_by_name, classify_io_error, FilesystemCapabilities};

/// Not empirically verified — no Windows machine available in the
/// environment this was built in. Implemented from documented Win32
/// behavior (`GetVolumePathNameW` + `GetVolumeInformationW`); see
/// "Open items" in dev-docs/design/filesystem-detection.md.
pub(super) fn probe(path: &Path) -> Result<FilesystemCapabilities> {
    let wide_path = to_wide(path);

    // `GetVolumeInformationW` needs a volume *root* path, not an
    // arbitrary directory on the volume — resolve it first, per the
    // pattern Microsoft's own docs recommend for this exact call.
    let mut root_buf = [0u16; 261];
    let resolved = unsafe {
        GetVolumePathNameW(wide_path.as_ptr(), root_buf.as_mut_ptr(), root_buf.len() as u32)
    };
    if resolved == 0 {
        return Err(classify_io_error(std::io::Error::last_os_error(), path.to_path_buf()));
    }

    let mut fs_name_buf = [0u16; 261];
    let mut max_component_len: u32 = 0;
    let mut flags: u32 = 0;
    let ok = unsafe {
        GetVolumeInformationW(
            root_buf.as_ptr(),
            std::ptr::null_mut(),
            0,
            std::ptr::null_mut(),
            &mut max_component_len,
            &mut flags,
            fs_name_buf.as_mut_ptr(),
            fs_name_buf.len() as u32,
        )
    };
    if ok == 0 {
        return Err(classify_io_error(std::io::Error::last_os_error(), path.to_path_buf()));
    }

    let name = from_wide(&fs_name_buf).to_lowercase();
    // `FILE_CASE_SENSITIVE_SEARCH` reflects modern per-directory
    // case-sensitivity opt-in (Windows 10+ WSL support), not a
    // reliable "is this whole volume case-sensitive" signal for
    // classic NTFS/exFAT/FAT32 — those default to unset (case
    // -insensitive), which is what this crate's case-collision check
    // needs to see to actually run. Best available single-call signal;
    // see "Open items" in dev-docs/design/filesystem-detection.md.
    let case_sensitive = flags & FILE_CASE_SENSITIVE_SEARCH != 0;

    // Host OS is Windows itself here, so the exFAT-write-integrity
    // risk (specific to macOS's `F_FULLFSYNC` implementation) never
    // applies — `classify_by_name`'s `host_is_macos` is always false.
    let (max_file_size, windows_naming_rules, timestamp_granularity, write_integrity_risk) =
        classify_by_name(&name, false);

    Ok(FilesystemCapabilities {
        name,
        case_sensitive,
        max_file_size,
        windows_naming_rules,
        timestamp_granularity,
        write_integrity_risk,
    })
}

fn to_wide(path: &Path) -> Vec<u16> {
    path.as_os_str().encode_wide().chain(iter::once(0)).collect()
}

fn from_wide(buf: &[u16]) -> String {
    let len = buf.iter().position(|&c| c == 0).unwrap_or(buf.len());
    String::from_utf16_lossy(&buf[..len])
}