use std::fs;
use std::path::{Path, PathBuf};
pub fn on_disk_path(opened: &Path) -> Option<PathBuf> {
let raw = opened.to_str()?.trim();
let path = strip(raw);
if path.is_empty() {
None
} else {
Some(PathBuf::from(path))
}
}
pub fn ptrace_pid(opened: &str) -> Option<&str> {
let rest = opened.trim().strip_prefix("ptrace://")?;
if rest.is_empty() || !rest.bytes().all(|byte| byte.is_ascii_digit()) {
None
} else {
Some(rest)
}
}
pub fn ptrace_exe(opened: &Path) -> Option<PathBuf> {
let pid = ptrace_pid(opened.to_str()?)?;
let link = fs::read_link(format!("/proc/{pid}/exe")).ok()?;
let raw = link.to_string_lossy();
let path = raw.strip_suffix(" (deleted)").unwrap_or(&raw);
if path.is_empty() {
None
} else {
Some(PathBuf::from(path))
}
}
fn strip(value: &str) -> &str {
let Some((scheme, rest)) = value.split_once("://") else {
return value;
};
if scheme.is_empty() || !scheme.chars().all(|ch| ch.is_ascii_alphanumeric()) {
return value;
}
if rest.is_empty() || rest.bytes().all(|byte| byte.is_ascii_digit()) {
return "";
}
rest
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn strips_path_bearing_uris() {
assert_eq!(
on_disk_path(Path::new("dbg://./hello")),
Some(PathBuf::from("./hello"))
);
assert_eq!(
on_disk_path(Path::new("file:///tmp/hello")),
Some(PathBuf::from("/tmp/hello"))
);
assert_eq!(
on_disk_path(Path::new("/tmp/hello")),
Some(PathBuf::from("/tmp/hello"))
);
}
#[test]
fn recognizes_ptrace_pid_uris() {
assert_eq!(ptrace_pid("ptrace://103133"), Some("103133"));
assert_eq!(ptrace_pid("dbg://./hello"), None);
assert_eq!(on_disk_path(Path::new("ptrace://103133")), None);
}
#[test]
fn resolves_current_process_executable() {
let uri = PathBuf::from(format!("ptrace://{}", std::process::id()));
let executable = ptrace_exe(&uri).expect("/proc/PID/exe");
assert!(executable.is_absolute());
}
}