sys-rs 0.1.1

ptrace-based Linux system tool reimplementations: strace, gcov, addr2line, debugger
Documentation
use nix::unistd::Pid;
use std::{
    fs::File,
    io::{BufRead, BufReader, Write},
    process::exit,
};

use sys_rs::{
    coverage::Cached,
    diag::Result,
    input::{args, env},
    process, profile, trace,
};

struct Wrapper {
    tracer: profile::Tracer,
}

impl Wrapper {
    pub fn new(path: &str) -> Result<Self> {
        Ok(Self {
            tracer: profile::Tracer::new(path)?,
        })
    }
}

fn write_cov_line(out: &mut File, fmt: &str, i: usize, line: &str) -> Result<()> {
    Ok(writeln!(out, "{fmt:>9}:{i:>5}:{line:<}")?)
}

fn process_file(path: &str, cached: &Cached) -> Result<()> {
    let file = File::open(path)?;
    let reader = BufReader::new(file);
    let out_path = format!("{path}.cov");

    if let Ok(mut out) = File::create(&out_path) {
        let (total_lines, covered) = reader.lines().enumerate().try_fold(
            (0usize, 0usize),
            |(_, covered), (idx, line)| -> Result<(usize, usize)> {
                let i = idx + 1;
                let line = line?;
                if let Some(count) = cached.coverage(path.to_string(), i) {
                    write_cov_line(&mut out, &format!("{count}"), i, &line)?;
                    Ok((i, covered + 1))
                } else {
                    write_cov_line(&mut out, "-", i, &line)?;
                    Ok((i, covered))
                }
            },
        )?;

        #[allow(clippy::cast_precision_loss)]
        let percentage = (covered as f64 / total_lines as f64) * 100.0;

        eprintln!("\nFile: '{path}'");
        eprintln!("Lines executed: {percentage:.2}% of {total_lines}");
        eprintln!("Creating '{out_path}'");
    } else {
        eprintln!("Warning: {out_path}: Could not create coverage file");
    }

    Ok(())
}

impl trace::Tracer for Wrapper {
    fn trace(&self, pid: Pid) -> Result<i32> {
        let process = process::Info::build(self.tracer.path(), pid)?;
        let mut cached = Cached::default();
        let ret = cached.trace_with_default_progress(&self.tracer, &process)?;
        cached
            .files()
            .iter()
            .try_for_each(|path| process_file(path, &cached))?;
        Ok(ret)
    }
}

fn main() -> Result<()> {
    let args = args()?;
    exit(trace::run::<Wrapper>(
        &Wrapper::new(args[0].to_str()?)?,
        &args,
        &env()?,
    )?)
}