service_install/install/init/
systemd.rs1#![allow(clippy::missing_errors_doc)]
2use std::ffi::OsStr;
7use std::path::{Component, Path, PathBuf};
8use std::time::Duration;
9use std::{fs, io};
10
11use crate::install::builder::Trigger;
12use crate::install::files::NoHomeError;
13
14pub use self::unit::FindExeError;
15use self::unit::Unit;
16
17use super::{ExeLocation, Mode, Params, PathCheckError, RSteps, SetupError, Steps, TearDownError};
18
19mod api;
20mod disable_existing;
21mod setup;
22mod teardown;
23mod unit;
24
25pub(crate) use disable_existing::disable_step;
26pub use disable_existing::DisableError;
27use sysinfo::{ProcessRefreshKind, ProcessesToUpdate, UpdateKind};
28
29#[derive(thiserror::Error, Debug)]
30pub enum SystemCtlError {
31 #[error("Could not run systemctl")]
32 Io(
33 #[from]
34 #[source]
35 std::io::Error,
36 ),
37 #[error("Systemctl failed: {reason}")]
38 Failed { reason: String },
39 #[error("Timed out trying to enable service")]
40 RestartTimeOut,
41 #[error("Timed out trying to disable service")]
42 DisableTimeOut,
43 #[error("Timed out trying to stop service")]
44 StopTimeOut,
45 #[error("Something send a signal to systemctl ending it before it could finish")]
46 Terminated,
47}
48
49#[derive(thiserror::Error, Debug)]
50pub enum Error {
51 #[error("Could not write out unit file to {path}")]
52 Writing {
53 #[source]
54 e: io::Error,
55 path: PathBuf,
56 },
57 #[error("Could not remove the unit files, error: {0}")]
58 Removing(#[source] io::Error),
59 #[error("Could not verify unit files where created by us, could not open them")]
60 Verifying(
61 #[from]
62 #[source]
63 unit::Error,
64 ),
65 #[error("Could not check if this system uses systemd")]
66 CheckingInitSys(
67 #[from]
68 #[source]
69 PathCheckError,
70 ),
71 #[error("Could not check if there is an existing service we will replace")]
72 CheckingRunning(#[source] SystemCtlError),
73 #[error("Could not enable the service")]
74 Enabling(#[source] api::Error),
75 #[error("Could not start the service")]
76 Starting(#[source] api::Error),
77 #[error("Could not restart the service")]
78 Restarting(#[source] api::Error),
79 #[error("Could not disable the service")]
80 Disabling(#[source] api::Error),
81 #[error("Could not stop the service")]
82 Stopping(#[source] api::Error),
83 #[error("Could not check if the service is active or not")]
84 CheckActive(#[source] api::Error),
85 #[error("Error while waiting for service to be started")]
86 WaitingForStart(#[source] api::WaitError),
87 #[error("Error while waiting for service to be stopped")]
88 WaitingForStop(#[source] api::WaitError),
89 #[error("Could not reload services")]
90 Reloading(#[source] api::Error),
91}
92
93pub(crate) fn path_is_systemd(path: &Path) -> Result<bool, PathCheckError> {
94 let path = path.canonicalize().map_err(PathCheckError)?;
95
96 Ok(path
97 .components()
98 .filter_map(|c| match c {
99 Component::Normal(cmp) => Some(cmp),
100 _other => None,
101 })
102 .filter_map(|c| c.to_str())
103 .any(|c| c == "systemd"))
104}
105
106pub(super) fn not_available() -> Result<bool, SetupError> {
108 use sysinfo::{Pid, System};
109 let mut s = System::new();
110 s.refresh_processes_specifics(
111 ProcessesToUpdate::Some([Pid::from(1)].as_slice()),
112 true,
113 ProcessRefreshKind::nothing().with_cmd(UpdateKind::Always),
114 );
115 let init_sys = &s
116 .process(Pid::from(1))
117 .expect("there should always be an init system")
118 .cmd()
119 .first()
120 .expect("we requested command");
121 Ok(!path_is_systemd(Path::new(init_sys)).map_err(Error::from)?)
122}
123
124pub(super) fn set_up_steps(params: &Params) -> Result<Steps, SetupError> {
125 let path_without_extension = match params.mode {
126 Mode::User => user_path()?,
127 Mode::System => system_path(),
128 }
129 .join(¶ms.name);
130
131 Ok(match params.trigger {
132 Trigger::OnSchedule(ref schedule) => {
133 setup::with_timer(&path_without_extension, params, schedule)
134 }
135 Trigger::OnBoot => setup::without_timer(&path_without_extension, params)?,
136 })
137}
138
139pub(super) fn tear_down_steps(mode: Mode) -> Result<Option<(RSteps, ExeLocation)>, TearDownError> {
140 let dir = match mode {
141 Mode::User => user_path()?,
142 Mode::System => system_path(),
143 };
144
145 let mut steps = Vec::new();
146 let mut exe_paths = Vec::new();
147
148 for entry in fs::read_dir(dir).unwrap() {
149 let path = entry.unwrap().path();
150 if path.is_dir() {
151 continue;
152 }
153 let Some(extension) = path.extension().and_then(OsStr::to_str) else {
154 continue;
155 };
156 let unit = Unit::from_path(path.clone()).unwrap();
157 if !unit.our_service() {
158 continue;
159 }
160 let Some(service_name) = path.file_stem().and_then(OsStr::to_str) else {
161 continue;
162 };
163
164 match extension {
165 "timer" => {
166 steps.extend(teardown::disable_then_remove_with_timer(
167 unit.path.clone(),
168 service_name,
169 mode,
170 ));
171 }
172 "service" => {
173 steps.extend(teardown::disable_then_remove_service(
174 unit.path.clone(),
175 service_name,
176 mode,
177 ));
178 exe_paths.push(unit.exe_path().map_err(TearDownError::FindingExePath)?);
179 }
180 _ => continue,
181 }
182 }
183
184 exe_paths.dedup();
185 match (steps.len(), exe_paths.as_slice()) {
186 (0, []) => Ok(None),
187 (0, [_, ..]) => unreachable!("if we get an exe path we got one service to remove"),
188 (1.., []) => Err(TearDownError::TimerWithoutService),
189 (1.., [exe_path]) => Ok(Some((steps, exe_path.clone()))),
190 (1.., _) => Err(TearDownError::MultipleExePaths(exe_paths)),
191 }
192}
193
194fn user_path() -> Result<PathBuf, NoHomeError> {
196 Ok(home::home_dir()
197 .ok_or(NoHomeError)?
198 .join(".config/systemd/user/"))
199}
200
201fn system_path() -> PathBuf {
203 PathBuf::from("/etc/systemd/system")
204}
205
206async fn enable(unit: &str, mode: Mode, and_start: bool) -> Result<(), Error> {
207 api::reload(mode).await.map_err(Error::Reloading)?;
208 api::enable_service(unit, mode)
209 .await
210 .map_err(Error::Enabling)?;
211 if and_start {
212 api::start_service(unit, mode)
213 .await
214 .map_err(Error::Starting)?;
215 tokio::time::sleep(Duration::from_secs(2)).await;
216 api::wait_for_active(unit, mode)
217 .await
218 .map_err(Error::WaitingForStart)?;
219 }
220 Ok(())
221}
222
223async fn restart(unit_file_name: &str, mode: Mode) -> Result<(), Error> {
224 api::restart(unit_file_name, mode)
225 .await
226 .map_err(Error::Restarting)
227}
228
229async fn disable(unit_file_name: &str, mode: Mode, and_stop: bool) -> Result<(), Error> {
230 api::disable_service(unit_file_name, mode)
231 .await
232 .map_err(Error::Disabling)?;
233 if and_stop {
234 stop(unit_file_name, mode).await?;
235 api::wait_for_inactive(unit_file_name, mode)
236 .await
237 .map_err(Error::WaitingForStop)?;
238 }
239 Ok(())
240}
241
242async fn stop(unit_file_name: &str, mode: Mode) -> Result<(), Error> {
243 api::stop_service(unit_file_name, mode)
244 .await
245 .map_err(Error::Stopping)
246}
247
248async fn is_active(unit_file_name: &str, mode: Mode) -> Result<bool, Error> {
249 api::is_active(unit_file_name, mode)
250 .await
251 .map_err(Error::CheckActive)
252}