running_process/broker/
fs_health.rs1use std::path::Path;
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub struct InodeUsage {
14 pub total: u64,
16 pub free: u64,
18}
19
20impl InodeUsage {
21 pub fn used(&self) -> u64 {
23 self.total.saturating_sub(self.free)
24 }
25
26 pub fn used_ratio(&self) -> f64 {
28 if self.total == 0 {
29 0.0
30 } else {
31 self.used() as f64 / self.total as f64
32 }
33 }
34}
35
36pub fn inode_usage(path: &Path) -> std::io::Result<Option<InodeUsage>> {
42 #[cfg(windows)]
43 {
44 let _ = path;
45 Ok(None)
46 }
47 #[cfg(unix)]
48 {
49 use std::os::unix::ffi::OsStrExt;
50
51 let bytes = path.as_os_str().as_bytes();
52 let c_path = std::ffi::CString::new(bytes)
53 .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidInput, err))?;
54 let mut stats: libc::statvfs = unsafe { std::mem::zeroed() };
55 let rc = unsafe { libc::statvfs(c_path.as_ptr(), &mut stats) };
56 if rc != 0 {
57 return Err(std::io::Error::last_os_error());
58 }
59 if stats.f_files == 0 {
60 return Ok(None);
61 }
62 #[allow(clippy::unnecessary_cast)]
64 let usage = InodeUsage {
65 total: stats.f_files as u64,
66 free: stats.f_favail as u64,
67 };
68 Ok(Some(usage))
69 }
70}
71
72pub fn daemon_data_dir_inode_usage() -> std::io::Result<Option<InodeUsage>> {
76 let dir = crate::client::paths::data_dir();
77 let mut probe: &Path = &dir;
78 while !probe.exists() {
79 match probe.parent() {
80 Some(parent) => probe = parent,
81 None => break,
82 }
83 }
84 inode_usage(probe)
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn used_ratio_handles_zero_total() {
93 let usage = InodeUsage { total: 0, free: 0 };
94 assert_eq!(usage.used_ratio(), 0.0);
95 }
96
97 #[test]
98 fn used_ratio_is_fractional() {
99 let usage = InodeUsage {
100 total: 100,
101 free: 25,
102 };
103 assert_eq!(usage.used(), 75);
104 assert!((usage.used_ratio() - 0.75).abs() < f64::EPSILON);
105 }
106
107 #[cfg(unix)]
108 #[test]
109 fn inode_usage_probes_temp_dir() {
110 let result = inode_usage(&std::env::temp_dir()).expect("statvfs on temp dir");
111 if let Some(usage) = result {
112 assert!(usage.total > 0);
113 assert!(usage.free <= usage.total);
114 }
115 }
116
117 #[cfg(windows)]
118 #[test]
119 fn inode_usage_is_not_applicable_on_windows() {
120 let result = inode_usage(&std::env::temp_dir()).expect("probe never fails on windows");
121 assert_eq!(result, None);
122 }
123
124 #[test]
125 fn daemon_data_dir_probe_never_panics() {
126 let _ = daemon_data_dir_inode_usage();
127 }
128}