service_install/install/
init.rs

1use 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/// Allowed init systems, set using: [`install::Spec::allowed_inits()`](super::Spec::allowed_inits)
22#[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
203// man 7 syntax
204//
205// For settings where quoting is allowed, the following general rules apply:
206// double quotes ("...") and single quotes ('...') may be used to wrap a whole
207// item (the opening quote may appear only at the beginning or after whitespace
208// that is not quoted, and the closing quote must be followed by whitespace
209// or the end of line), in which case everything until the next matching
210// quote becomes part of the same item. Quotes themselves are removed. C-style
211// escapes are supported.
212trait SystemdEscape {
213    fn systemd_escape(&self) -> String;
214}
215
216impl SystemdEscape for String {
217    fn systemd_escape(&self) -> String {
218        // escaped / -> // and escaped " -> /"
219        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}