use sha2::{Digest, Sha256};
use std::path::{Path, PathBuf};
use serde_json::json;
#[must_use]
pub fn sha256_hex_of(path: &Path) -> Option<String> {
let mut file = std::fs::File::open(path).ok()?;
let mut hasher = Sha256::new();
std::io::copy(&mut file, &mut hasher).ok()?;
Some(format!("{:x}", hasher.finalize()))
}
#[must_use]
pub fn resolve_symlink_target(target: &Path) -> Option<PathBuf> {
let md = std::fs::symlink_metadata(target).ok()?;
if !md.file_type().is_symlink() {
return None;
}
let link = std::fs::read_link(target).ok()?;
if link.is_absolute() {
return Some(link);
}
let parent = target.parent().unwrap_or_else(|| Path::new("."));
let base_abs = if parent.is_absolute() {
parent.to_path_buf()
} else {
std::env::current_dir().ok()?.join(parent)
};
Some(base_abs.join(link))
}
#[must_use]
pub fn kind_of(path: &Path) -> &'static str {
match std::fs::symlink_metadata(path) {
Ok(md) => {
let ft = md.file_type();
if ft.is_symlink() {
"symlink"
} else if ft.is_file() {
"file"
} else if ft.is_dir() {
"dir"
} else {
"unknown"
}
}
Err(_) => "missing",
}
}
#[must_use]
pub fn detect_preservation_capabilities(path: &Path) -> (serde_json::Value, bool) {
let mut owner = false;
let mut mode = false;
let mut timestamps = false;
let mut xattrs = false;
let acls = false;
let caps = false;
if std::fs::symlink_metadata(path).is_ok() {
mode = true;
timestamps = true;
owner = effective_uid_is_root();
#[cfg(any(
target_os = "linux",
target_os = "android",
target_os = "macos",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
{
xattrs = xattr::list(path).map(|_| true).unwrap_or(false);
}
}
let preservation = json!({
"owner": owner,
"mode": mode,
"timestamps": timestamps,
"xattrs": xattrs,
"acls": acls,
"caps": caps,
});
let supported = owner || mode || timestamps || xattrs || acls || caps;
(preservation, supported)
}
fn effective_uid_is_root() -> bool {
#[cfg(target_os = "linux")]
{
rustix::process::geteuid().as_raw() == 0
}
#[cfg(not(target_os = "linux"))]
{
false
}
}