service_install/install/files/
process_parent.rs

1use std::path::{Path, PathBuf};
2use std::process::Command;
3
4use itertools::Itertools;
5use sysinfo::Pid;
6
7use crate::install::init::PathCheckError;
8use crate::install::{init, InstallStep};
9
10#[derive(Debug)]
11pub(crate) enum IdRes {
12    /// Process locking up the file has no parent, must be orphaned
13    NoParent,
14    ParentIsInit {
15        init: init::System,
16        pid: Pid,
17    },
18    ParentNotInit {
19        parents: Vec<PathBuf>,
20        pid: Pid,
21    },
22}
23
24impl IdRes {
25    fn from_tree_and_pid(
26        tree: Vec<&Path>,
27        pid: Pid,
28        init_systems: &[init::System],
29    ) -> Result<IdRes, PathCheckError> {
30        let Some(direct_parent) = tree.first() else {
31            return Ok(IdRes::NoParent);
32        };
33
34        for init in init_systems {
35            if init.is_init_path(direct_parent)? {
36                return Ok(IdRes::ParentIsInit {
37                    init: init.clone(),
38                    pid,
39                });
40            }
41        }
42
43        Ok(IdRes::ParentNotInit {
44            pid,
45            parents: tree.into_iter().map(PathBuf::from).collect(),
46        })
47    }
48}
49
50pub(crate) fn list(
51    target: &Path,
52    init_systems: &[init::System],
53) -> Result<Vec<IdRes>, PathCheckError> {
54    use sysinfo::{ProcessRefreshKind, System, UpdateKind};
55
56    let mut s = System::new();
57    s.refresh_processes_specifics(
58        sysinfo::ProcessesToUpdate::All,
59        true,
60        ProcessRefreshKind::nothing()
61            .with_exe(UpdateKind::Always)
62            .with_cmd(UpdateKind::Always),
63    );
64
65    let using_target: Vec<_> = s
66        .processes()
67        .iter()
68        .map(|(_, process)| process)
69        .filter(|p| p.exe() == Some(target))
70        .collect();
71
72    let without_children = using_target.iter().filter(|p| {
73        if let Some(parent) = p.parent() {
74            !using_target.iter().any(|p| p.pid() == parent)
75        } else {
76            true
77        }
78    });
79
80    without_children
81        .copied()
82        .map(|p| {
83            let mut process = p;
84            let mut tree = Vec::new();
85
86            while let Some(parent) = process.parent() {
87                if let Some(parent) = s.process(parent) {
88                    process = parent;
89                    if let Some(exe) = process.exe() {
90                        tree.push(exe);
91                    } else {
92                        let cmd = process.cmd();
93                        let path = Path::new(&cmd[0]);
94                        tree.push(path);
95                    }
96                }
97            }
98            (tree, p.pid())
99        })
100        .map(|(tree, pid)| IdRes::from_tree_and_pid(tree, pid, init_systems))
101        .collect()
102}
103
104#[derive(Debug, thiserror::Error)]
105pub enum KillOldError {
106    #[error("Could not run the kill command")]
107    KillUnavailable(#[source] std::io::Error),
108    #[error("The kill command faild with: {0}")]
109    KillFailed(String),
110}
111
112pub struct KillOld {
113    pid: Pid,
114    parents: Vec<PathBuf>,
115}
116
117impl InstallStep for KillOld {
118    fn describe(&self, tense: crate::Tense) -> String {
119        match tense {
120            crate::Tense::Past => {
121                "there was a program running with the same name taking up the \
122                    install location it has been terminated."
123            }
124            crate::Tense::Questioning => {
125                "there is a program running with the same name taking up the \
126                    install location, terminate it?"
127            }
128            crate::Tense::Active => {
129                "there is a program running with the same name taking up the \
130                    install location, terminating it."
131            }
132            crate::Tense::Future => {
133                "there is a program running with the same name taking up the \
134                    install location, it will be terminated."
135            }
136        }
137        .to_string()
138    }
139
140    fn perform(
141        &mut self,
142    ) -> Result<Option<Box<dyn crate::install::RollbackStep>>, crate::install::InstallError> {
143        let output = Command::new("kill")
144            .arg("--signal")
145            .arg("TERM")
146            .arg(format!("{}", self.pid))
147            .output()
148            .map_err(KillOldError::KillUnavailable)
149            .map_err(crate::install::InstallError::KillOld)?;
150
151        if output.status.success() {
152            Ok(None)
153        } else {
154            let stderr = String::from_utf8_lossy(&output.stderr).to_string();
155            Err(crate::install::InstallError::KillOld(
156                KillOldError::KillFailed(stderr),
157            ))
158        }
159    }
160
161    fn describe_detailed(&self, tense: crate::Tense) -> String {
162        let list = if self.parents.len() == 1 {
163            self.parents
164                .first()
165                .expect("len just checked")
166                .display()
167                .to_string()
168        } else {
169            self.parents
170                .iter()
171                .map(|p| p.display().to_string())
172                .join("\n\twhich was started by: ")
173        };
174
175        match tense { // note final punctuation (question mark or dot) is added by printer
176            crate::Tense::Past => format!(
177                "there was a program running with the same name taking up the \
178            install location. It was was started by: {list}\nIt had to be terminated \
179            before we could continue."
180            ),
181            crate::Tense::Questioning => format!(
182                "there is a program running with the same name taking up the \
183            install location. It was was started by: {list}\nIt must be terminated \
184            before we can continue. Terminating might not work or the parent \
185            can restart the program. Do you wish to try to stop the program and \
186            continue installation?"
187            ),
188            crate::Tense::Active => format!(
189                "there is a program running with the same name taking up the \
190            install location. It was was started by: {list}\nIt must be terminated \
191            before we can continue. Terminating might not work or the parent \
192            can restart the program. Stopping the program and continuing installation."
193            ),
194            crate::Tense::Future => format!(
195                "there is a program running with the same name taking up the \
196            install location. It was was started by: {list}\nIt must be terminated \
197            before we can continue. Terminating might not work or the parent \
198            can restart the program. Will try to stop the program and continuing \
199            installation."
200            ),
201        }
202    }
203}
204
205pub(crate) fn kill_old_steps(pid: Pid, parents: Vec<PathBuf>) -> Box<dyn InstallStep> {
206    Box::new(KillOld { pid, parents })
207}