service_install/install/
init.rs1use std::collections::HashMap;
2use std::path::{Path, PathBuf};
3
4pub mod cron;
5pub(crate) mod extract_path;
6pub mod systemd;
7
8use sysinfo::Pid;
9
10use crate::install::RemoveStep;
11
12use self::systemd::FindExeError;
13
14use super::builder::Trigger;
15use super::files::{DisableError, NoHomeError, TargetInUseError};
16use super::{InstallStep, Mode};
17
18type Steps = Vec<Box<dyn InstallStep>>;
19type RSteps = Vec<Box<dyn RemoveStep>>;
20
21#[derive(Debug, Clone)]
23pub enum System {
24 Systemd,
25 Cron,
26}
27
28type ExeLocation = PathBuf;
29impl System {
30 pub(crate) fn name(&self) -> &'static str {
31 match self {
32 System::Systemd => "Systemd",
33 System::Cron => "Cron",
34 }
35 }
36 pub(crate) fn not_available(&self) -> Result<bool, SetupError> {
37 match self {
38 System::Systemd => systemd::not_available(),
39 System::Cron => Ok(cron::not_available()),
40 }
41 }
42 pub(crate) fn disable_steps(
43 &self,
44 target: &Path,
45 pid: Pid,
46 mode: Mode,
47 run_as: Option<&str>,
48 ) -> Result<Vec<Box<dyn InstallStep>>, TargetInUseError> {
49 match self {
50 System::Systemd => Ok(systemd::disable_step(target, mode).map_err(DisableError::from)?),
51 System::Cron => {
52 Ok(cron::disable::step(target, pid, run_as).map_err(DisableError::from)?)
53 }
54 }
55 }
56 pub(crate) fn set_up_steps(&self, params: &Params) -> Result<Steps, SetupError> {
57 match self {
58 System::Systemd => systemd::set_up_steps(params),
59 System::Cron => cron::set_up_steps(params),
60 }
61 }
62 pub(crate) fn tear_down_steps(
63 &self,
64 bin_name: &str,
65 mode: Mode,
66 user: Option<&str>,
67 ) -> Result<Option<(RSteps, ExeLocation)>, TearDownError> {
68 match self {
69 System::Systemd => systemd::tear_down_steps(mode),
70 System::Cron => cron::tear_down_steps(bin_name, mode, user),
71 }
72 }
73
74 pub(crate) fn all() -> Vec<System> {
75 vec![Self::Systemd, Self::Cron]
76 }
77
78 pub(crate) fn is_init_path(&self, path: &Path) -> Result<bool, PathCheckError> {
79 match self {
80 System::Systemd => systemd::path_is_systemd(path),
81 System::Cron => Ok(cron::is_init_path(path)),
82 }
83 }
84}
85
86#[derive(Debug, thiserror::Error)]
87#[error("The path could not be resolved, error {0}")]
88pub struct PathCheckError(std::io::Error);
89
90#[derive(thiserror::Error, Debug)]
91pub enum SetupError {
92 #[error("systemd specific error")]
93 Systemd(
94 #[from]
95 #[source]
96 systemd::Error,
97 ),
98 #[error("Error while setting up crontab rule")]
99 Cron(
100 #[from]
101 #[source]
102 cron::setup::Error,
103 ),
104 #[error("could not find current users home dir")]
105 NoHome(#[from] NoHomeError),
106}
107
108#[derive(thiserror::Error, Debug)]
109pub enum TearDownError {
110 #[error("Cron specific error")]
111 Cron(
112 #[from]
113 #[source]
114 cron::teardown::Error,
115 ),
116 #[error("Error while setting up systemd service")]
117 Systemd(
118 #[from]
119 #[source]
120 systemd::Error,
121 ),
122 #[error("Could not find current users home dir")]
123 NoHome(
124 #[from]
125 #[source]
126 NoHomeError,
127 ),
128 #[error("No service file while there is a timer file")]
129 TimerWithoutService,
130 #[error("Could not find path to executable")]
131 FindingExePath(
132 #[from]
133 #[source]
134 FindExeError,
135 ),
136 #[error(
137 "Found multiple different paths in services, do not know which to remove, paths: {0:?}"
138 )]
139 MultipleExePaths(Vec<PathBuf>),
140}
141
142#[derive(Debug, Clone)]
143pub(crate) struct Params {
144 pub(crate) name: String,
145 pub(crate) bin_name: &'static str,
146 pub(crate) description: Option<String>,
147
148 pub(crate) exe_path: PathBuf,
149 pub(crate) exe_args: Vec<String>,
150 pub(crate) environment: HashMap<String, String>,
151 pub(crate) working_dir: Option<PathBuf>,
152
153 pub(crate) trigger: Trigger,
154 pub(crate) run_as: Option<String>,
155 pub(crate) mode: Mode,
156}
157
158impl Params {
159 fn description(&self) -> String {
160 self.description
161 .clone()
162 .unwrap_or_else(|| format!("starts {}", self.name))
163 }
164}
165
166pub(crate) const COMMENT_PREAMBLE: &str = "# created by: ";
167pub(crate) const COMMENT_SUFFIX: &str = " during its installation\n# might get removed by it in the future.\n# Remove this comment to prevent that";
168
169fn autogenerated_comment(bin_name: &str) -> String {
170 format!("{COMMENT_PREAMBLE}'{bin_name}'{COMMENT_SUFFIX}")
171}
172
173trait ShellEscape {
174 fn shell_escaped(&self) -> String;
175}
176
177impl ShellEscape for std::path::PathBuf {
178 fn shell_escaped(&self) -> String {
179 let path = self.display().to_string();
180 let path = std::borrow::Cow::Owned(path);
181 let path = shell_escape::unix::escape(path);
182 path.into_owned()
183 }
184}
185
186impl ShellEscape for &std::path::Path {
187 fn shell_escaped(&self) -> String {
188 let path = self.display().to_string();
189 let path = std::borrow::Cow::Owned(path);
190 let path = shell_escape::unix::escape(path);
191 path.into_owned()
192 }
193}
194
195impl ShellEscape for String {
196 fn shell_escaped(&self) -> String {
197 let s = std::borrow::Cow::Borrowed(self.as_str());
198 let s = shell_escape::unix::escape(s);
199 s.into_owned()
200 }
201}
202
203trait SystemdEscape {
213 fn systemd_escape(&self) -> String;
214}
215
216impl SystemdEscape for String {
217 fn systemd_escape(&self) -> String {
218 let mut quotes_escaped = self.replace('"', "\\\"");
220 quotes_escaped.push('"');
221 quotes_escaped.insert(0, '"');
222 quotes_escaped
223 }
224}
225
226impl SystemdEscape for &std::path::Path {
227 fn systemd_escape(&self) -> String {
228 self.display().to_string().systemd_escape()
229 }
230}
231
232impl SystemdEscape for &str {
233 fn systemd_escape(&self) -> String {
234 self.to_string().systemd_escape()
235 }
236}
237
238impl SystemdEscape for std::path::PathBuf {
239 fn systemd_escape(&self) -> String {
240 self.display().to_string().systemd_escape()
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn escape_with_exclamation() {
250 let s = "message with spaces/1/11:00..16:30/!"
251 .to_string()
252 .systemd_escape();
253 assert_eq!(&s, "\"message with spaces/1/11:00..16:30/!\"");
254 }
255
256 #[test]
257 fn escape_single_letter() {
258 let s = "v".to_string().systemd_escape();
259 assert_eq!(&s, "\"v\"");
260 }
261}