radare2 0.2.2

Rust integration helpers for radare2 core plugins
//! Generic radare2 IO URI and debuggee-path handling.

use std::fs;
use std::path::{Path, PathBuf};

/// Filesystem path encoded by an opened path or IO URI.
///
/// Returns `None` for debugger URIs that contain only a process id, such as
/// `ptrace://1234`, and for other schemes with no on-disk path.
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))
    }
}

/// PID from a `ptrace://PID` URI, when the URI contains only the PID.
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)
    }
}

/// Resolve `/proc/PID/exe` for a `ptrace://PID` URI.
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))
    }
}

/// Strip a recognized r2 IO scheme from a path-bearing URI.
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());
    }
}