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
22pub 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 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}