use std::path::Path;
use thiserror::Error;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct NativeStorageCapabilities {
pub filesystem: String,
pub logical_bytes_per_sector: u32,
pub physical_bytes_per_sector_for_atomicity: u32,
pub physical_bytes_per_sector_for_performance: u32,
pub effective_physical_bytes_per_sector_for_atomicity: u32,
pub sectors_per_allocation_unit: u32,
pub allocation_unit_bytes: u64,
pub sparse_files: bool,
pub block_cloning: bool,
}
#[derive(Debug, Error)]
pub enum NativeStorageCapabilityError {
#[error("native storage capability discovery is unavailable on this target")]
UnsupportedTarget,
#[error("native storage capability root is invalid: {0}")]
InvalidRoot(String),
#[error("native storage capability probe failed: {0}")]
Platform(String),
#[error("native storage capability geometry is invalid")]
InvalidGeometry,
}
pub fn probe_native_storage_capabilities(
root: &Path,
) -> Result<NativeStorageCapabilities, NativeStorageCapabilityError> {
let metadata = std::fs::symlink_metadata(root)
.map_err(|error| NativeStorageCapabilityError::InvalidRoot(error.to_string()))?;
if !metadata.is_dir() || metadata.file_type().is_symlink() {
return Err(NativeStorageCapabilityError::InvalidRoot(
"root must be an existing non-link directory".to_owned(),
));
}
probe_platform(root)
}
#[cfg(windows)]
#[allow(unsafe_code)]
fn probe_platform(root: &Path) -> Result<NativeStorageCapabilities, NativeStorageCapabilityError> {
use std::os::windows::ffi::OsStrExt;
use windows::Win32::Storage::FileSystem::{
GetDiskFreeSpaceW, GetVolumeInformationW, GetVolumePathNameW,
};
use windows::Win32::System::SystemServices::{
FILE_SUPPORTS_BLOCK_REFCOUNTING, FILE_SUPPORTS_SPARSE_FILES,
};
use windows::core::PCWSTR;
let canonical = std::fs::canonicalize(root)
.map_err(|error| NativeStorageCapabilityError::InvalidRoot(error.to_string()))?;
let mut encoded = canonical.as_os_str().encode_wide().collect::<Vec<_>>();
encoded.push(0);
let mut volume_path = vec![0_u16; 32_768];
unsafe {
GetVolumePathNameW(PCWSTR(encoded.as_ptr()), &mut volume_path)
.map_err(|error| NativeStorageCapabilityError::Platform(error.to_string()))?;
}
let volume_length = volume_path
.iter()
.position(|value| *value == 0)
.ok_or_else(|| {
NativeStorageCapabilityError::Platform("volume path was not NUL-terminated".to_owned())
})?;
volume_path.truncate(volume_length + 1);
let mut sectors_per_allocation_unit = 0_u32;
let mut logical_bytes_per_sector = 0_u32;
unsafe {
GetDiskFreeSpaceW(
PCWSTR(volume_path.as_ptr()),
Some(&raw mut sectors_per_allocation_unit),
Some(&raw mut logical_bytes_per_sector),
None,
None,
)
.map_err(|error| NativeStorageCapabilityError::Platform(error.to_string()))?;
}
let storage = probe_root_storage(root)?;
let mut flags = 0_u32;
let mut filesystem_name = vec![0_u16; 256];
unsafe {
GetVolumeInformationW(
PCWSTR(volume_path.as_ptr()),
None,
None,
None,
Some(&raw mut flags),
Some(&mut filesystem_name),
)
.map_err(|error| NativeStorageCapabilityError::Platform(error.to_string()))?;
}
let filesystem_length = filesystem_name
.iter()
.position(|value| *value == 0)
.unwrap_or(filesystem_name.len());
let filesystem = String::from_utf16(
filesystem_name
.get(..filesystem_length)
.ok_or(NativeStorageCapabilityError::InvalidGeometry)?,
)
.map_err(|error| NativeStorageCapabilityError::Platform(error.to_string()))?;
let allocation_unit_bytes = u64::from(logical_bytes_per_sector)
.checked_mul(u64::from(sectors_per_allocation_unit))
.ok_or(NativeStorageCapabilityError::InvalidGeometry)?;
if logical_bytes_per_sector == 0
|| sectors_per_allocation_unit == 0
|| allocation_unit_bytes == 0
|| storage.LogicalBytesPerSector != logical_bytes_per_sector
|| storage.PhysicalBytesPerSectorForAtomicity == 0
|| storage.PhysicalBytesPerSectorForPerformance == 0
|| storage.FileSystemEffectivePhysicalBytesPerSectorForAtomicity == 0
|| filesystem.is_empty()
{
return Err(NativeStorageCapabilityError::InvalidGeometry);
}
Ok(NativeStorageCapabilities {
filesystem,
logical_bytes_per_sector,
physical_bytes_per_sector_for_atomicity: storage.PhysicalBytesPerSectorForAtomicity,
physical_bytes_per_sector_for_performance: storage.PhysicalBytesPerSectorForPerformance,
effective_physical_bytes_per_sector_for_atomicity: storage
.FileSystemEffectivePhysicalBytesPerSectorForAtomicity,
sectors_per_allocation_unit,
allocation_unit_bytes,
sparse_files: flags & FILE_SUPPORTS_SPARSE_FILES != 0,
block_cloning: flags & FILE_SUPPORTS_BLOCK_REFCOUNTING != 0,
})
}
#[cfg(windows)]
#[allow(unsafe_code)]
fn probe_root_storage(
root: &Path,
) -> Result<windows::Win32::Storage::FileSystem::FILE_STORAGE_INFO, NativeStorageCapabilityError> {
use std::mem::size_of;
use std::os::windows::fs::OpenOptionsExt;
use std::os::windows::io::AsRawHandle;
use windows::Win32::Foundation::HANDLE;
use windows::Win32::Storage::FileSystem::{
FILE_FLAG_BACKUP_SEMANTICS, FILE_READ_ATTRIBUTES, FILE_SHARE_DELETE, FILE_SHARE_READ,
FILE_SHARE_WRITE, FILE_STORAGE_INFO, FileStorageInfo, GetFileInformationByHandleEx,
};
let directory = std::fs::OpenOptions::new()
.access_mode(FILE_READ_ATTRIBUTES.0)
.share_mode(FILE_SHARE_READ.0 | FILE_SHARE_WRITE.0 | FILE_SHARE_DELETE.0)
.custom_flags(FILE_FLAG_BACKUP_SEMANTICS.0)
.open(root)
.map_err(|error| NativeStorageCapabilityError::Platform(error.to_string()))?;
let mut storage = FILE_STORAGE_INFO::default();
unsafe {
GetFileInformationByHandleEx(
HANDLE(directory.as_raw_handle()),
FileStorageInfo,
(&raw mut storage).cast(),
u32::try_from(size_of::<FILE_STORAGE_INFO>())
.map_err(|_| NativeStorageCapabilityError::InvalidGeometry)?,
)
}
.map_err(|error| NativeStorageCapabilityError::Platform(error.to_string()))?;
Ok(storage)
}
#[cfg(target_os = "macos")]
#[path = "storage_capabilities_macos.rs"]
mod macos;
#[cfg(target_os = "macos")]
fn probe_platform(root: &Path) -> Result<NativeStorageCapabilities, NativeStorageCapabilityError> {
macos::probe(root)
}
#[cfg(target_os = "linux")]
#[path = "storage_capabilities_linux.rs"]
mod linux;
#[cfg(target_os = "linux")]
fn probe_platform(root: &Path) -> Result<NativeStorageCapabilities, NativeStorageCapabilityError> {
linux::probe(root)
}
#[cfg(not(any(windows, target_os = "macos", target_os = "linux")))]
fn probe_platform(_root: &Path) -> Result<NativeStorageCapabilities, NativeStorageCapabilityError> {
Err(NativeStorageCapabilityError::UnsupportedTarget)
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(windows)]
#[test]
fn windows_probe_reports_nonzero_exact_volume_geometry()
-> Result<(), Box<dyn std::error::Error>> {
let directory = tempfile::tempdir()?;
let capabilities = probe_native_storage_capabilities(directory.path())?;
assert!(!capabilities.filesystem.is_empty());
assert!(capabilities.logical_bytes_per_sector > 0);
assert!(capabilities.physical_bytes_per_sector_for_atomicity > 0);
assert!(capabilities.physical_bytes_per_sector_for_performance > 0);
assert!(capabilities.effective_physical_bytes_per_sector_for_atomicity > 0);
assert!(capabilities.sectors_per_allocation_unit > 0);
assert_eq!(
capabilities.allocation_unit_bytes,
u64::from(capabilities.logical_bytes_per_sector)
* u64::from(capabilities.sectors_per_allocation_unit)
);
Ok(())
}
#[test]
fn non_directory_root_is_rejected_before_platform_discovery()
-> Result<(), Box<dyn std::error::Error>> {
let directory = tempfile::tempdir()?;
let file = directory.path().join("file");
std::fs::write(&file, b"not-a-root")?;
assert!(matches!(
probe_native_storage_capabilities(&file),
Err(NativeStorageCapabilityError::InvalidRoot(_))
));
Ok(())
}
}