indexbus_platform_ops/linux/
fs.rs

1//! Small filesystem probes used by preflight checks.
2//!
3//! These helpers are Linux-specific (they use `statfs`).
4
5use std::path::Path;
6
7use crate::errors::{Error, Result};
8
9/// Returns true if `path` is on a `hugetlbfs` mount (best-effort).
10///
11/// `hugetlbfs` magic: 0x958458f6.
12pub fn is_hugetlbfs(path: &Path) -> Result<bool> {
13    let mut s: libc::statfs = unsafe { std::mem::zeroed() };
14    let c_path = std::ffi::CString::new(path.to_string_lossy().as_bytes())
15        .map_err(|e| Error::msg(format!("invalid path for CString: {e}")))?;
16
17    let rc = unsafe { libc::statfs(c_path.as_ptr(), &mut s as *mut libc::statfs) };
18    if rc != 0 {
19        return Err(Error::msg(format!(
20            "statfs({}) failed: {}",
21            path.display(),
22            std::io::Error::last_os_error()
23        )));
24    }
25
26    Ok((s.f_type as u64) == 0x9584_58f6)
27}
28
29/// Best-effort writability probe for an mmap/SHM directory.
30///
31/// On `hugetlbfs`, small writes may return `EINVAL`; this uses `set_len(2MiB)` instead.
32pub fn probe_dir_writable(dir: &Path) -> Result<()> {
33    let path = dir.join(format!(".indexbus_write_test_{}", std::process::id()));
34
35    let res = (|| -> Result<()> {
36        let mut f = std::fs::OpenOptions::new()
37            .create(true)
38            .write(true)
39            .truncate(true)
40            .open(&path)
41            .map_err(|e| Error::msg(format!("dir not writable (open {}): {e}", path.display())))?;
42
43        if is_hugetlbfs(dir).unwrap_or(false) {
44            const TWO_MIB: u64 = 2 * 1024 * 1024;
45            f.set_len(TWO_MIB).map_err(|e| {
46                Error::msg(format!(
47                    "dir not writable (set_len {} to {} bytes): {e}",
48                    path.display(),
49                    TWO_MIB
50                ))
51            })?;
52        } else {
53            use std::io::Write;
54            f.write_all(b"ok\n").map_err(|e| {
55                Error::msg(format!("dir not writable (write {}): {e}", path.display()))
56            })?;
57        }
58
59        Ok(())
60    })();
61
62    let _ = std::fs::remove_file(&path);
63    res
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn is_hugetlbfs_errors_on_missing_path() {
72        let missing = std::path::PathBuf::from(format!(
73            "/tmp/indexbus_missing_{}_{}",
74            std::process::id(),
75            crate::time::monotonic_now_ns()
76        ));
77        let err = is_hugetlbfs(&missing).unwrap_err();
78        assert!(err.to_string().contains("statfs("));
79    }
80
81    #[test]
82    fn probe_dir_writable_succeeds_for_temp_dir() {
83        let dir = std::env::temp_dir();
84        probe_dir_writable(&dir).unwrap();
85    }
86
87    #[test]
88    fn probe_dir_writable_errors_when_not_a_dir() {
89        let base = std::env::temp_dir().join(format!(
90            "indexbus_ops_probe_{}_{}",
91            std::process::id(),
92            crate::time::monotonic_now_ns()
93        ));
94        std::fs::write(&base, "not a dir").unwrap();
95
96        let err = probe_dir_writable(&base).unwrap_err();
97        assert!(err.to_string().contains("dir not writable"));
98
99        let _ = std::fs::remove_file(&base);
100    }
101}