cloud_terrastodon_zombies/
lib.rs

1use cloud_terrastodon_user_input::Choice;
2use cloud_terrastodon_user_input::FzfArgs;
3use cloud_terrastodon_user_input::pick_many;
4use eyre::Context;
5use eyre::bail;
6use std::collections::HashSet;
7use std::path::Path;
8use tracing::debug;
9use tracing::info;
10
11/// Kill any processes whose exe path is in the given dirs or a child of one.
12/// Fails if any of the given dirs do not exist.
13pub fn prompt_kill_processes_using_dirs(
14    dirs: impl IntoIterator<Item = impl AsRef<Path>>,
15    header: String,
16) -> eyre::Result<()> {
17    let mut system = sysinfo::System::new_all();
18    system.refresh_all();
19
20    let mut parents = HashSet::new();
21    for dir in dirs {
22        let dir = dir.as_ref().to_path_buf();
23        let dir = dir
24            .canonicalize()
25            .wrap_err(format!("Failed to canonicalize given dir {dir:?}"))?;
26        parents.insert(dir);
27    }
28
29    // Iterate over all processes and find those matching the pattern
30    let mut to_kill = Vec::new();
31    for (pid, process) in system.processes() {
32        let Some(exe_path) = process.exe() else {
33            continue;
34        };
35        let Some(exe_dir) = exe_path.parent() else {
36            continue;
37        };
38        let exe_dir = exe_dir
39            .canonicalize()
40            .wrap_err(format!("Failed to canonicalize exe dir {exe_dir:?}"))?;
41        let is_in_dir = exe_dir
42            .ancestors()
43            .any(|ancestor| parents.contains(ancestor));
44        if is_in_dir {
45            to_kill.push(Choice {
46                key: format!(
47                    "ID: {}, Name: {}, Path: {:?}",
48                    pid,
49                    process.name().to_string_lossy(),
50                    process.exe()
51                ),
52                value: (pid, process),
53            });
54        }
55    }
56    if to_kill.is_empty() {
57        return Ok(());
58    }
59    let to_kill = pick_many(FzfArgs {
60        choices: to_kill,
61        header: Some(header),
62        ..Default::default()
63    })?;
64    for Choice {
65        key,
66        value: (_pid, process),
67    } in to_kill
68    {
69        debug!("Killing: {key}");
70        let signal_sent_success = process.kill();
71        if !signal_sent_success {
72            bail!("Failed to send kill signal to {key}");
73        }
74        let exit_status = process.wait();
75        info!("Killed  {key} with exit status: {exit_status:#?}");
76    }
77
78    Ok(())
79}
80
81#[cfg(test)]
82mod test {
83    use crate::prompt_kill_processes_using_dirs;
84    use cloud_terrastodon_pathing::AppDir;
85
86    #[test]
87    fn main() {
88        // Creating a System instance
89        let mut system = sysinfo::System::new_all();
90
91        // Refresh system processes information
92        system.refresh_all();
93
94        // Define the pattern to search for
95        let pattern = "terraform-provider";
96
97        // Iterate over all processes and find those matching the pattern
98        for (pid, process) in system.processes() {
99            let name = process.name().to_string_lossy();
100            if !name.contains(pattern) {
101                continue;
102            }
103            println!("ID: {}, Name: {}, Path: {:?}", pid, name, process.exe());
104        }
105    }
106    #[test]
107    #[ignore]
108    fn kill() {
109        prompt_kill_processes_using_dirs(
110            [
111                AppDir::Imports.as_path_buf(),
112                AppDir::Processed.as_path_buf(),
113            ],
114            "Found the following processes, select the ones you want to kill".to_string(),
115        )
116        .unwrap();
117    }
118}