Skip to main content

ytil_sys/
lsof.rs

1use std::path::PathBuf;
2use std::process::Command;
3use std::str::FromStr;
4
5use itertools::Itertools as _;
6use rootcause::prelude::ResultExt;
7use rootcause::report;
8use ytil_cmd::CmdExt as _;
9
10#[derive(Debug)]
11pub enum ProcessFilter<'a> {
12    Pid(&'a str),
13    Name(&'a str),
14}
15
16#[derive(Debug)]
17pub struct ProcessDescription {
18    pub pid: String,
19    pub cwd: PathBuf,
20}
21
22/// Retrieves process descriptions using the lsof command.
23///
24/// # Errors
25/// - lsof command execution or output parsing fails.
26pub fn lsof(process_filter: &ProcessFilter) -> rootcause::Result<Vec<ProcessDescription>> {
27    let cmd = "lsof";
28
29    let process_filter = match process_filter {
30        ProcessFilter::Pid(pid) => ["-p", pid],
31        ProcessFilter::Name(name) => ["-c", name],
32    };
33    let mut args = vec!["-F", "n"];
34    args.extend(process_filter);
35    args.extend(["-a", "-d", "cwd"]);
36
37    let stdout = Command::new(cmd)
38        .args(&args)
39        .exec()
40        .context("error running cmd")
41        .attach_with(|| format!("cmd={cmd:?} args={args:?}"))?
42        .exit_ok()
43        .context("error cmd exit not ok")
44        .attach_with(|| format!("cmd={cmd:?} args={args:?}"))?
45        .stdout;
46
47    let output = str::from_utf8(&stdout)?;
48    parse_lsof_output(output)
49}
50
51fn parse_lsof_output(output: &str) -> rootcause::Result<Vec<ProcessDescription>> {
52    let mut out = vec![];
53    // The hardcoded 3 is tight to the lsof args.
54    // Changes to lsof args will have impact on the chunks size.
55    for mut line in &output.lines().chunks(3) {
56        let pid = line
57            .next()
58            .ok_or_else(|| report!("error missing pid in lsof line"))?
59            .trim_start_matches('p');
60        line.next().ok_or_else(|| report!("error missing f in lsof line"))?;
61        let cwd = line
62            .next()
63            .ok_or_else(|| report!("error missing cwd in lsof line"))?
64            .trim_start_matches('n');
65
66        out.push(ProcessDescription {
67            pid: pid.to_owned(),
68            cwd: PathBuf::from_str(cwd)
69                .context("error constructing PathBuf from cwd")
70                .attach_with(|| format!("cwd={cwd:?}"))?,
71        });
72    }
73    Ok(out)
74}