use std::ffi::OsStr;
use std::process::{Command, ExitStatus, Stdio};
use anyhow::Result;
use thiserror::Error;
pub const SUDO_BIN: &str = "sudo";
pub const SYSTEMD_RUN_BIN: &str = "systemd-run";
pub const SYSTEMCTL_BIN: &str = "systemctl";
pub fn systemd_cmd_timer(time: u32, description: &str, unit: &str, cmd: &[&str]) -> Result<()> {
let _ = systemd_remove_timer(unit);
let _ = systemctl_reset_failed_timer(unit);
let time = format!("{time}");
let mut systemd_cmd = vec![
"--quiet",
"--system",
"--on-active",
&time,
"--timer-property=AccuracySec=1s",
"--description",
description,
"--unit",
unit,
"--",
];
systemd_cmd.extend(cmd);
systemd_run(&systemd_cmd)
}
fn systemctl_reset_failed_timer(unit: &str) -> Result<()> {
let result = cmd_systemctl(["--quiet", "--system", "reset-failed", unit])
.stderr(Stdio::null())
.status()
.map_err(Err::Systemctl);
if unit.ends_with(".service") {
let unit = format!("{}.timer", unit.trim_end_matches(".service"));
systemctl_reset_failed_timer(&unit).or_else(|_| cmd_assert_status(result?))
} else {
cmd_assert_status(result?)
}
}
pub fn systemd_has_timer(unit: &str) -> Result<bool> {
let cmd = cmd_systemctl(["--system", "--no-pager", "--quiet", "status", unit])
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.map_err(Err::Systemctl)?;
match cmd.code() {
Some(0) | Some(3) => Ok(true),
Some(4) if unit.ends_with(".service") => {
let unit = format!("{}.timer", unit.trim_end_matches(".service"));
systemd_has_timer(&unit)
}
Some(4) => Ok(false),
_ => cmd_assert_status(cmd).map(|_| false),
}
}
pub fn systemd_remove_timer(unit: &str) -> Result<()> {
let result = cmd_systemctl(["--system", "--quiet", "stop", unit])
.stderr(Stdio::null())
.status()
.map_err(Err::Systemctl);
if unit.ends_with(".service") {
let unit = format!("{}.timer", unit.trim_end_matches(".service"));
systemd_remove_timer(&unit).or_else(|_| cmd_assert_status(result?))
} else {
cmd_assert_status(result?)
}
}
fn systemd_run<I, S>(args: I) -> Result<()>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
cmd_assert_status(cmd_systemd_run(args).status().map_err(Err::SystemdRun)?)
}
fn cmd_systemd_run<I, S>(args: I) -> Command
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let mut cmd = Command::new(SUDO_BIN);
cmd.arg("--");
cmd.arg(SYSTEMD_RUN_BIN);
cmd.args(args);
cmd
}
fn cmd_systemctl<I, S>(args: I) -> Command
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let mut cmd = Command::new(SUDO_BIN);
cmd.arg("--");
cmd.arg(SYSTEMCTL_BIN);
cmd.args(args);
cmd
}
fn cmd_assert_status(status: ExitStatus) -> Result<()> {
if !status.success() {
return Err(Err::Status(status).into());
}
Ok(())
}
#[derive(Debug, Error)]
pub enum Err {
#[error("failed to invoke systemd-run command")]
SystemdRun(#[source] std::io::Error),
#[error("failed to invoke systemctl command")]
Systemctl(#[source] std::io::Error),
#[error("systemd exited with non-zero status code: {0}")]
Status(std::process::ExitStatus),
}