1use std::ffi::OsStr;
2use std::process::{Command, ExitStatus, Stdio};
3
4use anyhow::Result;
5use thiserror::Error;
6
7pub const SUDO_BIN: &str = "sudo";
9
10pub const SYSTEMD_RUN_BIN: &str = "systemd-run";
12
13pub const SYSTEMCTL_BIN: &str = "systemctl";
15
16pub fn systemd_cmd_timer(time: u32, description: &str, unit: &str, cmd: &[&str]) -> Result<()> {
20 let _ = systemd_remove_timer(unit);
22 let _ = systemctl_reset_failed_timer(unit);
23
24 let time = format!("{time}");
26 let mut systemd_cmd = vec![
27 "--quiet",
28 "--system",
29 "--on-active",
30 &time,
31 "--timer-property=AccuracySec=1s",
32 "--description",
33 description,
34 "--unit",
35 unit,
36 "--",
37 ];
38 systemd_cmd.extend(cmd);
39 systemd_run(&systemd_cmd)
40}
41
42fn systemctl_reset_failed_timer(unit: &str) -> Result<()> {
49 let result = cmd_systemctl(["--quiet", "--system", "reset-failed", unit])
51 .stderr(Stdio::null())
52 .status()
53 .map_err(Err::Systemctl);
54
55 if unit.ends_with(".service") {
57 let unit = format!("{}.timer", unit.trim_end_matches(".service"));
58 systemctl_reset_failed_timer(&unit).or_else(|_| cmd_assert_status(result?))
59 } else {
60 cmd_assert_status(result?)
61 }
62}
63
64pub fn systemd_has_timer(unit: &str) -> Result<bool> {
71 let cmd = cmd_systemctl(["--system", "--no-pager", "--quiet", "status", unit])
73 .stdout(Stdio::null())
74 .stderr(Stdio::null())
75 .status()
76 .map_err(Err::Systemctl)?;
77
78 match cmd.code() {
80 Some(0) | Some(3) => Ok(true),
81 Some(4) if unit.ends_with(".service") => {
82 let unit = format!("{}.timer", unit.trim_end_matches(".service"));
83 systemd_has_timer(&unit)
84 }
85 Some(4) => Ok(false),
86 _ => cmd_assert_status(cmd).map(|_| false),
87 }
88}
89
90pub fn systemd_remove_timer(unit: &str) -> Result<()> {
98 let result = cmd_systemctl(["--system", "--quiet", "stop", unit])
100 .stderr(Stdio::null())
101 .status()
102 .map_err(Err::Systemctl);
103
104 if unit.ends_with(".service") {
106 let unit = format!("{}.timer", unit.trim_end_matches(".service"));
107 systemd_remove_timer(&unit).or_else(|_| cmd_assert_status(result?))
108 } else {
109 cmd_assert_status(result?)
110 }
111}
112
113fn systemd_run<I, S>(args: I) -> Result<()>
115where
116 I: IntoIterator<Item = S>,
117 S: AsRef<OsStr>,
118{
119 cmd_assert_status(cmd_systemd_run(args).status().map_err(Err::SystemdRun)?)
120}
121
122fn cmd_systemd_run<I, S>(args: I) -> Command
133where
134 I: IntoIterator<Item = S>,
135 S: AsRef<OsStr>,
136{
137 let mut cmd = Command::new(SUDO_BIN);
138 cmd.arg("--");
139 cmd.arg(SYSTEMD_RUN_BIN);
140 cmd.args(args);
141 cmd
142}
143
144fn cmd_systemctl<I, S>(args: I) -> Command
146where
147 I: IntoIterator<Item = S>,
148 S: AsRef<OsStr>,
149{
150 let mut cmd = Command::new(SUDO_BIN);
151 cmd.arg("--");
152 cmd.arg(SYSTEMCTL_BIN);
153 cmd.args(args);
154 cmd
155}
156
157fn cmd_assert_status(status: ExitStatus) -> Result<()> {
161 if !status.success() {
162 return Err(Err::Status(status).into());
163 }
164 Ok(())
165}
166
167#[derive(Debug, Error)]
168pub enum Err {
169 #[error("failed to invoke systemd-run command")]
170 SystemdRun(#[source] std::io::Error),
171
172 #[error("failed to invoke systemctl command")]
173 Systemctl(#[source] std::io::Error),
174
175 #[error("systemd exited with non-zero status code: {0}")]
176 Status(std::process::ExitStatus),
177}