use super::probe::{Probe, WriterProbe};
use std::path::{Path, PathBuf};
pub(super) struct ProcFsProbe {
proc_root: PathBuf,
}
impl Default for ProcFsProbe {
fn default() -> Self {
Self {
proc_root: PathBuf::from("/proc"),
}
}
}
impl ProcFsProbe {
#[cfg(test)]
fn with_root(proc_root: PathBuf) -> Self {
Self { proc_root }
}
fn scan(&self, path: &Path) -> bool {
let Ok(target) = std::fs::canonicalize(path) else {
return false;
};
let Ok(entries) = std::fs::read_dir(&self.proc_root) else {
return false;
};
for entry in entries.flatten() {
if entry
.file_name()
.to_str()
.and_then(|s| s.parse::<u32>().ok())
.is_none()
{
continue;
}
if pid_holds_writable(&entry.path(), &target) {
return true;
}
}
false
}
}
impl WriterProbe for ProcFsProbe {
fn writer_state(&self, path: &Path) -> Probe {
if self.scan(path) {
Probe::Held
} else {
Probe::Free
}
}
}
fn pid_holds_writable(proc_pid: &Path, target: &Path) -> bool {
let Ok(entries) = std::fs::read_dir(proc_pid.join("fd")) else {
return false;
};
for entry in entries.flatten() {
if !std::fs::read_link(entry.path())
.and_then(std::fs::canonicalize)
.is_ok_and(|link| link == target)
{
continue;
}
let Some(fd) = entry
.file_name()
.to_str()
.and_then(|s| s.parse::<u32>().ok())
else {
continue;
};
if fdinfo_is_writable(proc_pid, fd) {
return true;
}
}
false
}
fn fdinfo_is_writable(proc_pid: &Path, fd: u32) -> bool {
let Ok(contents) = std::fs::read_to_string(proc_pid.join("fdinfo").join(fd.to_string())) else {
return false;
};
for line in contents.lines() {
if let Some(rest) = line.strip_prefix("flags:") {
return rest.trim().chars().last().is_some_and(|c| c != '0');
}
}
false
}
#[cfg(test)]
mod tests;