use std::fs;
use std::os::unix::fs::MetadataExt;
use std::path::Path;
pub fn ensure_mount_rw_exec(path: &Path) -> Result<(), String> {
match crate::fs::mount::ensure_rw_exec(&crate::fs::mount::ProcStatfsInspector, path) {
Ok(()) => Ok(()),
Err(_) => Err(format!(
"Filesystem at '{}' not suitable or ambiguous (requires rw and exec)",
path.display()
)),
}
}
pub fn check_hardlink_hazard(path: &Path) -> std::io::Result<bool> {
if let Ok(md) = fs::symlink_metadata(path) {
let ft = md.file_type();
if ft.is_file() {
let n = md.nlink();
return Ok(n > 1);
}
}
Ok(false)
}
pub fn check_suid_sgid_risk(path: &Path) -> std::io::Result<bool> {
let inspect_path = if let Ok(md) = fs::symlink_metadata(path) {
if md.file_type().is_symlink() {
if let Some(p) = crate::fs::meta::resolve_symlink_target(path) {
p
} else {
path.to_path_buf()
}
} else {
path.to_path_buf()
}
} else {
path.to_path_buf()
};
if let Ok(meta) = fs::metadata(&inspect_path) {
let mode = meta.mode();
let risk = (mode & 0o6000) != 0; return Ok(risk);
}
Ok(false)
}
pub fn check_immutable(path: &Path) -> Result<(), String> {
let Ok(output) = std::process::Command::new("lsattr")
.arg("-d")
.arg(path) .output()
else {
return Ok(());
};
if !output.status.success() {
return Ok(()); }
let stdout = String::from_utf8_lossy(&output.stdout);
for line in stdout.lines() {
if let Some(attrs) = line.split_whitespace().next() {
if attrs.contains('i') {
return Err(format!(
"Target '{}' is immutable (chattr +i). Run: chattr -i -- {}",
path.display(),
path.display()
));
}
}
}
Ok(())
}
pub fn check_source_trust(source: &Path, force: bool) -> Result<(), String> {
let meta = fs::symlink_metadata(source).map_err(|e| format!("{e}"))?;
let mode = meta.mode();
if (mode & 0o002) != 0 && !force {
return Err(format!(
"Untrusted source (world-writable): {}. Pass --force to override.",
source.display()
));
}
if meta.uid() != 0 && !force {
return Err(format!(
"Untrusted source (not root-owned): {}. Pass --force to override.",
source.display()
));
}
ensure_mount_rw_exec(source)?;
if let Ok(home) = std::env::var("HOME") {
let home_p = Path::new(&home);
if source.starts_with(home_p) && !force {
return Err(format!(
"Untrusted source under HOME: {}. Pass --force to override.",
source.display()
));
}
}
Ok(())
}