Skip to main content

acyclic_fs/native_mount/
storage_capabilities.rs

1//! Native filesystem capability discovery for qualification and adapter admission.
2
3use std::path::Path;
4use thiserror::Error;
5
6/// Exact immutable facts reported by the filesystem hosting one native root.
7#[derive(Clone, Debug, Eq, PartialEq)]
8pub struct NativeStorageCapabilities {
9    /// Stable filesystem implementation name reported by the host.
10    pub filesystem: String,
11    /// Logical sector bytes used by filesystem offsets and allocation units.
12    pub logical_bytes_per_sector: u32,
13    /// Physical sector bytes required for atomic writes by the storage stack.
14    pub physical_bytes_per_sector_for_atomicity: u32,
15    /// Physical sector bytes preferred for storage-stack performance.
16    pub physical_bytes_per_sector_for_performance: u32,
17    /// Filesystem-adjusted physical atomicity sector used for qualification.
18    pub effective_physical_bytes_per_sector_for_atomicity: u32,
19    /// Logical sectors in one filesystem allocation unit.
20    pub sectors_per_allocation_unit: u32,
21    /// Checked product of sector size and sectors per allocation unit.
22    pub allocation_unit_bytes: u64,
23    /// Whether the host advertises durable sparse-file allocation semantics.
24    pub sparse_files: bool,
25    /// Whether the host advertises same-volume block-reference cloning.
26    pub block_cloning: bool,
27}
28
29/// Native capability discovery failed before a mount or qualification run.
30#[derive(Debug, Error)]
31pub enum NativeStorageCapabilityError {
32    /// This target has no implemented native capability probe yet.
33    #[error("native storage capability discovery is unavailable on this target")]
34    UnsupportedTarget,
35    /// The supplied root is missing, inaccessible, or not a directory.
36    #[error("native storage capability root is invalid: {0}")]
37    InvalidRoot(String),
38    /// The operating system rejected a volume-information request.
39    #[error("native storage capability probe failed: {0}")]
40    Platform(String),
41    /// Reported geometry was zero or could not be represented exactly.
42    #[error("native storage capability geometry is invalid")]
43    InvalidGeometry,
44}
45
46/// Discovers exact non-destructive storage facts for the volume containing
47/// `root`.
48///
49/// # Errors
50///
51/// Rejects a missing/non-directory root, unsupported target, platform query
52/// failure, zero geometry, or allocation-unit overflow.
53pub fn probe_native_storage_capabilities(
54    root: &Path,
55) -> Result<NativeStorageCapabilities, NativeStorageCapabilityError> {
56    let metadata = std::fs::symlink_metadata(root)
57        .map_err(|error| NativeStorageCapabilityError::InvalidRoot(error.to_string()))?;
58    if !metadata.is_dir() || metadata.file_type().is_symlink() {
59        return Err(NativeStorageCapabilityError::InvalidRoot(
60            "root must be an existing non-link directory".to_owned(),
61        ));
62    }
63    probe_platform(root)
64}
65
66#[cfg(windows)]
67#[allow(unsafe_code)]
68fn probe_platform(root: &Path) -> Result<NativeStorageCapabilities, NativeStorageCapabilityError> {
69    use std::os::windows::ffi::OsStrExt;
70    use windows::Win32::Storage::FileSystem::{
71        GetDiskFreeSpaceW, GetVolumeInformationW, GetVolumePathNameW,
72    };
73    use windows::Win32::System::SystemServices::{
74        FILE_SUPPORTS_BLOCK_REFCOUNTING, FILE_SUPPORTS_SPARSE_FILES,
75    };
76    use windows::core::PCWSTR;
77
78    let canonical = std::fs::canonicalize(root)
79        .map_err(|error| NativeStorageCapabilityError::InvalidRoot(error.to_string()))?;
80    let mut encoded = canonical.as_os_str().encode_wide().collect::<Vec<_>>();
81    encoded.push(0);
82    let mut volume_path = vec![0_u16; 32_768];
83    // SAFETY: both buffers are live, NUL-terminated where required, and the
84    // output slice has its exact writable length for the duration of the call.
85    unsafe {
86        GetVolumePathNameW(PCWSTR(encoded.as_ptr()), &mut volume_path)
87            .map_err(|error| NativeStorageCapabilityError::Platform(error.to_string()))?;
88    }
89    let volume_length = volume_path
90        .iter()
91        .position(|value| *value == 0)
92        .ok_or_else(|| {
93            NativeStorageCapabilityError::Platform("volume path was not NUL-terminated".to_owned())
94        })?;
95    volume_path.truncate(volume_length + 1);
96
97    let mut sectors_per_allocation_unit = 0_u32;
98    let mut logical_bytes_per_sector = 0_u32;
99    // SAFETY: the volume path remains NUL-terminated and every supplied output
100    // pointer references a live `u32` for the complete synchronous call.
101    unsafe {
102        GetDiskFreeSpaceW(
103            PCWSTR(volume_path.as_ptr()),
104            Some(&raw mut sectors_per_allocation_unit),
105            Some(&raw mut logical_bytes_per_sector),
106            None,
107            None,
108        )
109        .map_err(|error| NativeStorageCapabilityError::Platform(error.to_string()))?;
110    }
111    let storage = probe_root_storage(root)?;
112    let mut flags = 0_u32;
113    let mut filesystem_name = vec![0_u16; 256];
114    // SAFETY: the input path is NUL-terminated; the output slice and flags
115    // pointer are valid and exclusively borrowed for this synchronous call.
116    unsafe {
117        GetVolumeInformationW(
118            PCWSTR(volume_path.as_ptr()),
119            None,
120            None,
121            None,
122            Some(&raw mut flags),
123            Some(&mut filesystem_name),
124        )
125        .map_err(|error| NativeStorageCapabilityError::Platform(error.to_string()))?;
126    }
127    let filesystem_length = filesystem_name
128        .iter()
129        .position(|value| *value == 0)
130        .unwrap_or(filesystem_name.len());
131    let filesystem = String::from_utf16(
132        filesystem_name
133            .get(..filesystem_length)
134            .ok_or(NativeStorageCapabilityError::InvalidGeometry)?,
135    )
136    .map_err(|error| NativeStorageCapabilityError::Platform(error.to_string()))?;
137    let allocation_unit_bytes = u64::from(logical_bytes_per_sector)
138        .checked_mul(u64::from(sectors_per_allocation_unit))
139        .ok_or(NativeStorageCapabilityError::InvalidGeometry)?;
140    if logical_bytes_per_sector == 0
141        || sectors_per_allocation_unit == 0
142        || allocation_unit_bytes == 0
143        || storage.LogicalBytesPerSector != logical_bytes_per_sector
144        || storage.PhysicalBytesPerSectorForAtomicity == 0
145        || storage.PhysicalBytesPerSectorForPerformance == 0
146        || storage.FileSystemEffectivePhysicalBytesPerSectorForAtomicity == 0
147        || filesystem.is_empty()
148    {
149        return Err(NativeStorageCapabilityError::InvalidGeometry);
150    }
151    Ok(NativeStorageCapabilities {
152        filesystem,
153        logical_bytes_per_sector,
154        physical_bytes_per_sector_for_atomicity: storage.PhysicalBytesPerSectorForAtomicity,
155        physical_bytes_per_sector_for_performance: storage.PhysicalBytesPerSectorForPerformance,
156        effective_physical_bytes_per_sector_for_atomicity: storage
157            .FileSystemEffectivePhysicalBytesPerSectorForAtomicity,
158        sectors_per_allocation_unit,
159        allocation_unit_bytes,
160        sparse_files: flags & FILE_SUPPORTS_SPARSE_FILES != 0,
161        block_cloning: flags & FILE_SUPPORTS_BLOCK_REFCOUNTING != 0,
162    })
163}
164
165#[cfg(windows)]
166#[allow(unsafe_code)]
167fn probe_root_storage(
168    root: &Path,
169) -> Result<windows::Win32::Storage::FileSystem::FILE_STORAGE_INFO, NativeStorageCapabilityError> {
170    use std::mem::size_of;
171    use std::os::windows::fs::OpenOptionsExt;
172    use std::os::windows::io::AsRawHandle;
173    use windows::Win32::Foundation::HANDLE;
174    use windows::Win32::Storage::FileSystem::{
175        FILE_FLAG_BACKUP_SEMANTICS, FILE_READ_ATTRIBUTES, FILE_SHARE_DELETE, FILE_SHARE_READ,
176        FILE_SHARE_WRITE, FILE_STORAGE_INFO, FileStorageInfo, GetFileInformationByHandleEx,
177    };
178
179    // FileStorageInfo is available on a directory handle. Querying the
180    // existing root avoids a temporary file, filesystem mutation, and USN
181    // journal advance on every capability probe.
182    let directory = std::fs::OpenOptions::new()
183        .access_mode(FILE_READ_ATTRIBUTES.0)
184        .share_mode(FILE_SHARE_READ.0 | FILE_SHARE_WRITE.0 | FILE_SHARE_DELETE.0)
185        .custom_flags(FILE_FLAG_BACKUP_SEMANTICS.0)
186        .open(root)
187        .map_err(|error| NativeStorageCapabilityError::Platform(error.to_string()))?;
188    let mut storage = FILE_STORAGE_INFO::default();
189    // SAFETY: the handle is borrowed from the live root directory and the output
190    // pointer names one correctly sized, exclusively borrowed structure.
191    unsafe {
192        GetFileInformationByHandleEx(
193            HANDLE(directory.as_raw_handle()),
194            FileStorageInfo,
195            (&raw mut storage).cast(),
196            u32::try_from(size_of::<FILE_STORAGE_INFO>())
197                .map_err(|_| NativeStorageCapabilityError::InvalidGeometry)?,
198        )
199    }
200    .map_err(|error| NativeStorageCapabilityError::Platform(error.to_string()))?;
201    Ok(storage)
202}
203
204#[cfg(target_os = "macos")]
205#[path = "storage_capabilities_macos.rs"]
206mod macos;
207
208#[cfg(target_os = "macos")]
209fn probe_platform(root: &Path) -> Result<NativeStorageCapabilities, NativeStorageCapabilityError> {
210    macos::probe(root)
211}
212
213#[cfg(target_os = "linux")]
214#[path = "storage_capabilities_linux.rs"]
215mod linux;
216
217#[cfg(target_os = "linux")]
218fn probe_platform(root: &Path) -> Result<NativeStorageCapabilities, NativeStorageCapabilityError> {
219    linux::probe(root)
220}
221
222#[cfg(not(any(windows, target_os = "macos", target_os = "linux")))]
223fn probe_platform(_root: &Path) -> Result<NativeStorageCapabilities, NativeStorageCapabilityError> {
224    Err(NativeStorageCapabilityError::UnsupportedTarget)
225}
226
227#[cfg(test)]
228mod tests {
229    use super::*;
230
231    #[cfg(windows)]
232    #[test]
233    fn windows_probe_reports_nonzero_exact_volume_geometry()
234    -> Result<(), Box<dyn std::error::Error>> {
235        let directory = tempfile::tempdir()?;
236        let capabilities = probe_native_storage_capabilities(directory.path())?;
237        assert!(!capabilities.filesystem.is_empty());
238        assert!(capabilities.logical_bytes_per_sector > 0);
239        assert!(capabilities.physical_bytes_per_sector_for_atomicity > 0);
240        assert!(capabilities.physical_bytes_per_sector_for_performance > 0);
241        assert!(capabilities.effective_physical_bytes_per_sector_for_atomicity > 0);
242        assert!(capabilities.sectors_per_allocation_unit > 0);
243        assert_eq!(
244            capabilities.allocation_unit_bytes,
245            u64::from(capabilities.logical_bytes_per_sector)
246                * u64::from(capabilities.sectors_per_allocation_unit)
247        );
248        Ok(())
249    }
250
251    #[test]
252    fn non_directory_root_is_rejected_before_platform_discovery()
253    -> Result<(), Box<dyn std::error::Error>> {
254        let directory = tempfile::tempdir()?;
255        let file = directory.path().join("file");
256        std::fs::write(&file, b"not-a-root")?;
257        assert!(matches!(
258            probe_native_storage_capabilities(&file),
259            Err(NativeStorageCapabilityError::InvalidRoot(_))
260        ));
261        Ok(())
262    }
263}