use anyhow::{bail, Context, Result};
use std::path::Path;
pub fn available_disk_space(path: &Path) -> Result<u64> {
use std::ffi::CString;
use std::os::unix::ffi::OsStrExt;
let c_path = CString::new(path.as_os_str().as_bytes()).context("Path contains null bytes")?;
unsafe {
let mut stat: libc::statvfs = std::mem::zeroed();
if libc::statvfs(c_path.as_ptr(), &mut stat) != 0 {
bail!(
"Failed to check disk space: {}",
std::io::Error::last_os_error()
);
}
Ok(stat.f_bavail as u64 * stat.f_frsize)
}
}
pub fn format_size(bytes: u64) -> String {
if bytes >= 1024 * 1024 * 1024 {
format!("{:.1}GB", bytes as f64 / (1024.0 * 1024.0 * 1024.0))
} else if bytes >= 1024 * 1024 {
format!("{:.0}MB", bytes as f64 / (1024.0 * 1024.0))
} else {
format!("{:.0}KB", bytes as f64 / 1024.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn available_disk_space_root_returns_a_value() {
let _ = available_disk_space(Path::new("/")).unwrap();
}
#[test]
fn available_disk_space_path_with_null_byte_errors() {
use std::os::unix::ffi::OsStrExt;
let bad = std::ffi::OsStr::from_bytes(b"/tmp\0/x");
let err = available_disk_space(Path::new(bad)).unwrap_err();
assert!(
err.to_string().to_lowercase().contains("null"),
"expected null-byte error, got: {}",
err
);
}
#[test]
fn format_size_basic_units() {
assert_eq!(format_size(1024 * 1024), "1MB");
assert_eq!(format_size(500 * 1024 * 1024), "500MB");
assert_eq!(format_size(2 * 1024 * 1024 * 1024), "2.0GB");
assert_eq!(format_size(512 * 1024), "512KB");
}
#[test]
fn format_size_zero_renders_zero_kb() {
assert_eq!(format_size(0), "0KB");
}
#[test]
fn format_size_sub_kb_rounds_to_zero_kb() {
assert_eq!(format_size(100), "0KB");
assert_eq!(format_size(1023), "1KB"); }
#[test]
fn format_size_boundary_between_mb_and_gb() {
assert_eq!(format_size(1024 * 1024 * 1024), "1.0GB");
assert_eq!(format_size(1024 * 1024 * 1024 - 1), "1024MB");
}
#[test]
fn format_size_boundary_between_kb_and_mb() {
assert_eq!(format_size(1024 * 1024), "1MB");
assert_eq!(format_size(1024 * 1024 - 1), "1024KB");
}
#[test]
fn format_size_large_values_still_in_gb() {
assert_eq!(format_size(1500u64 * 1024 * 1024 * 1024), "1500.0GB");
}
#[test]
fn format_size_u64_max_does_not_panic() {
let _ = format_size(u64::MAX);
}
}