service_install/install/files/
process_parent.rs1use 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 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 { 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}