service_install/install/init/
cron.rs1use crate::install::init::cron::teardown::path_from_rule;
2use std::fmt;
3use std::io::Write;
4use std::path::{Path, PathBuf};
5use std::process::{Command, Stdio};
6
7use super::{Params, SetupError, Steps};
8use crate::install::{RollbackError, RollbackStep};
9use crate::Tense;
10
11pub mod disable;
12pub mod setup;
13pub mod teardown;
14
15pub(crate) use setup::set_up_steps;
16use sysinfo::ProcessesToUpdate;
17pub(crate) use teardown::tear_down_steps;
18
19pub(super) fn not_available() -> bool {
20 use sysinfo::{ProcessRefreshKind, System, UpdateKind};
21 let mut s = System::new();
22 s.refresh_processes_specifics(
23 ProcessesToUpdate::All,
24 true,
25 ProcessRefreshKind::nothing().with_cmd(UpdateKind::Always),
26 );
27 let cron_running = s.processes().iter().any(|(_, process)| {
28 process
29 .cmd()
30 .iter()
31 .any(|part| part.to_string_lossy().ends_with("/cron"))
32 });
33 !cron_running
34}
35
36pub(crate) fn is_init_path(path: &Path) -> bool {
37 path.ends_with("cron")
38}
39
40struct RollbackImpossible;
41impl RollbackStep for RollbackImpossible {
42 fn perform(&mut self) -> Result<(), RollbackError> {
43 Err(RollbackError::Impossible)
44 }
45
46 fn describe(&self, _: Tense) -> String {
47 "Rollback of cron setup is not possible.\n\tsuggestion: inspect and fix crontab manually\n\tnote: you can safely edit crontab using `crontab -e`".to_string()
48 }
49}
50
51#[derive(Debug, Clone)]
52pub(crate) struct Line {
53 pos: usize,
55 text: String,
56}
57
58impl Line {
59 fn text(&self) -> &str {
60 &self.text
61 }
62
63 fn comment(&self) -> bool {
64 self.text.trim_start().starts_with('#')
65 }
66
67 fn exec(&self) -> Option<PathBuf> {
68 if self.comment() {
69 Some(path_from_rule(&self.text))
70 } else {
71 None
72 }
73 }
74}
75
76impl fmt::Display for Line {
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 let Self { pos, text } = self;
79 f.write_fmt(format_args!("{pos}: {text}"))
80 }
81}
82
83#[must_use]
84fn crontab_lines(text: &str) -> Vec<Line> {
85 const HEADER_ADDED_BY_LIST_CMD: &str = "# DO NOT EDIT THIS FILE";
86 let header_lines = if text.starts_with(HEADER_ADDED_BY_LIST_CMD) {
87 3
88 } else {
89 0
90 };
91
92 text.lines()
93 .skip(header_lines)
94 .map(str::to_owned)
95 .enumerate()
96 .map(|(source, text)| Line { text, pos: source })
97 .collect()
98}
99
100#[derive(Debug, thiserror::Error)]
101pub enum GetCrontabError {
102 #[error("Could not run the crontab program")]
103 CouldNotRun(#[source] std::io::Error),
104 #[error("Command `crontab -l` failed, stderr: \"{stderr}\"\n\t")]
105 CommandFailed { stderr: String },
106}
107
108fn current_crontab(user: Option<&str>) -> Result<Vec<Line>, GetCrontabError> {
109 let mut command = Command::new("crontab");
110 command.arg("-l");
111 if let Some(user) = user {
112 command.arg("-u");
113 command.arg(user);
114 }
115
116 let output = command.output().map_err(GetCrontabError::CouldNotRun)?;
117
118 if output.status.success() {
119 let stdout = String::from_utf8(output.stdout).expect("crontab should return utf8");
120 let crontab = crontab_lines(&stdout);
121 return Ok(crontab);
122 }
123
124 let stderr = String::from_utf8(output.stderr)
125 .expect("crontab should return utf8")
126 .trim()
127 .to_string();
128
129 if stderr.starts_with("no crontab for") {
130 return Ok(Vec::new());
131 }
132
133 Err(GetCrontabError::CommandFailed { stderr })
134}
135
136#[derive(Debug, thiserror::Error)]
137pub enum SetCrontabError {
138 #[error("Could not run the crontab program")]
139 CouldNotRun(#[source] std::io::Error),
140 #[error("Command `crontab -` failed, stderr:\n\t{stderr}")]
141 CommandFailed { stderr: String },
142 #[error("Failed to open crontab stdin")]
143 StdinClosed,
144 #[error("Error while writing to crontab's stdin")]
145 WritingStdin(#[source] std::io::Error),
146 #[error("Could not wait on output of crontab program")]
147 FailedToWait(#[source] std::io::Error),
148}
149
150fn set_crontab(new_crontab: &str, user: Option<&str>) -> Result<(), SetCrontabError> {
151 let mut command = Command::new("crontab");
152 command.arg("-");
153 if let Some(user) = user {
154 command.arg("-u");
155 command.arg(user);
156 }
157 let mut child = command
158 .stdin(Stdio::piped())
159 .stdout(Stdio::piped())
160 .stderr(Stdio::piped())
161 .spawn()
162 .map_err(SetCrontabError::CouldNotRun)?;
163
164 let mut stdin = child.stdin.take().ok_or(SetCrontabError::StdinClosed)?;
165 stdin
166 .write_all(new_crontab.as_bytes())
167 .map_err(SetCrontabError::WritingStdin)?;
168 drop(stdin);
169
170 let output = child
171 .wait_with_output()
172 .map_err(SetCrontabError::FailedToWait)?;
173 if output.status.success() {
174 Ok(())
175 } else {
176 let stderr = String::from_utf8(output.stderr).expect("crontab should return utf8");
177 Err(SetCrontabError::CommandFailed { stderr })
178 }
179}