1use std::path::PathBuf;
2use std::process::Command;
3use std::str::FromStr;
4
5use color_eyre::eyre::Context;
6use color_eyre::eyre::eyre;
7use itertools::Itertools as _;
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) -> color_eyre::Result<Vec<ProcessDescription>> {
40 let cmd = "lsof";
41
42 let process_filter = match process_filter {
43 ProcessFilter::Pid(pid) => ["-p", pid],
44 ProcessFilter::Name(name) => ["-c", name],
45 };
46 let mut args = vec!["-F", "n"];
47 args.extend(process_filter);
48 args.extend(["-a", "-d", "cwd"]);
49
50 let stdout = Command::new(cmd)
51 .args(&args)
52 .exec()
53 .wrap_err_with(|| eyre!("error running cmd | cmd={cmd:?} args={args:?}"))?
54 .exit_ok()
55 .wrap_err_with(|| eyre!("error cmd exit not ok | cmd={cmd:?} args={args:?}"))?
56 .stdout;
57
58 let output = str::from_utf8(&stdout)?;
59 parse_lsof_output(output)
60}
61
62fn parse_lsof_output(output: &str) -> color_eyre::Result<Vec<ProcessDescription>> {
63 let mut out = vec![];
64 for mut line in &output.lines().chunks(3) {
67 let pid = line
68 .next()
69 .ok_or_else(|| eyre!("error missing pid in lsof line"))?
70 .trim_start_matches('p');
71 line.next().ok_or_else(|| eyre!("error missing f in lsof line"))?;
72 let cwd = line
73 .next()
74 .ok_or_else(|| eyre!("error missing cwd in lsof line"))?
75 .trim_start_matches('n');
76
77 out.push(ProcessDescription {
78 pid: pid.to_owned(),
79 cwd: PathBuf::from_str(cwd)
80 .wrap_err_with(|| format!("error constructing PathBuf from cwd | cwd={cwd:?}"))?,
81 });
82 }
83 Ok(out)
84}