acyclic_fs/native_mount/
storage_capabilities.rs1use std::path::Path;
4use thiserror::Error;
5
6#[derive(Clone, Debug, Eq, PartialEq)]
8pub struct NativeStorageCapabilities {
9 pub filesystem: String,
11 pub logical_bytes_per_sector: u32,
13 pub physical_bytes_per_sector_for_atomicity: u32,
15 pub physical_bytes_per_sector_for_performance: u32,
17 pub effective_physical_bytes_per_sector_for_atomicity: u32,
19 pub sectors_per_allocation_unit: u32,
21 pub allocation_unit_bytes: u64,
23 pub sparse_files: bool,
25 pub block_cloning: bool,
27}
28
29#[derive(Debug, Error)]
31pub enum NativeStorageCapabilityError {
32 #[error("native storage capability discovery is unavailable on this target")]
34 UnsupportedTarget,
35 #[error("native storage capability root is invalid: {0}")]
37 InvalidRoot(String),
38 #[error("native storage capability probe failed: {0}")]
40 Platform(String),
41 #[error("native storage capability geometry is invalid")]
43 InvalidGeometry,
44}
45
46pub 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 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 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 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 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 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}