use std::path::Path;
#[cfg(unix)]
#[must_use]
pub(crate) fn free_and_capacity(path: &Path) -> Option<(u64, u64)> {
use std::os::unix::ffi::OsStrExt;
let c_path = std::ffi::CString::new(path.as_os_str().as_bytes()).ok()?;
let mut stats = unsafe { std::mem::zeroed::<libc::statvfs>() };
if unsafe { libc::statvfs(c_path.as_ptr(), std::ptr::addr_of_mut!(stats)) } != 0 {
return None;
}
let frsize = stats.f_frsize;
if frsize == 0 {
return None;
}
Some((
block_bytes(stats.f_bavail, frsize),
block_bytes(stats.f_blocks, frsize),
))
}
#[cfg(unix)]
fn block_bytes(blocks: impl Into<u64>, frsize: u64) -> u64 {
blocks.into().saturating_mul(frsize)
}
#[cfg(any(windows, test))]
#[must_use]
fn needs_unc_separator(wide: &[u16]) -> bool {
let backslash = u16::from(b'\\');
wide.starts_with(&[backslash, backslash])
&& !matches!(wide.get(2), Some(&c) if c == u16::from(b'?') || c == u16::from(b'.'))
&& !wide.ends_with(&[backslash])
&& !wide.ends_with(&[u16::from(b'/')])
}
#[cfg(windows)]
#[must_use]
pub(crate) fn free_and_capacity(path: &Path) -> Option<(u64, u64)> {
use windows_sys::Win32::Storage::FileSystem::GetDiskFreeSpaceExW;
let mut wide = crate::util::wide_path(path)?;
if needs_unc_separator(&wide) {
wide.push(u16::from(b'\\'));
}
wide.push(0);
let mut free = 0_u64;
let mut capacity = 0_u64;
let ok = unsafe {
GetDiskFreeSpaceExW(
wide.as_ptr(),
std::ptr::addr_of_mut!(free),
std::ptr::addr_of_mut!(capacity),
std::ptr::null_mut(),
)
};
(ok != 0).then_some((free, capacity))
}
#[cfg(not(any(unix, windows)))]
#[must_use]
pub(crate) fn free_and_capacity(_path: &Path) -> Option<(u64, u64)> {
None
}
#[cfg(test)]
mod tests {
use super::needs_unc_separator;
fn wide(path: &str) -> Vec<u16> {
path.encode_utf16().collect()
}
#[test]
fn only_a_unc_shaped_path_needs_the_trailing_separator() {
assert!(needs_unc_separator(&wide(r"\\srv\share")));
assert!(!needs_unc_separator(&wide(r"\\srv\share\")));
assert!(!needs_unc_separator(&wide(r"\\srv/share/")));
assert!(!needs_unc_separator(&wide(r"\\?\C:\ws")));
assert!(!needs_unc_separator(&wide(r"\\.\C:")));
assert!(!needs_unc_separator(&wide(r"C:\ws")));
}
}