A library for writing host-specific, single-binary configuration management and deployment tools
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

113 lines
3.2 KiB

use crate::async_utils::sleep;
use crate::command_runner::{CommandRunner, SetuidCommandRunner};
use crate::symbols::Symbol;
use async_trait::async_trait;
use std::error::Error;
use std::ffi::OsStr;
use std::path::Path;
use std::time::Duration;
#[derive(Debug)]
pub struct UserService<'a, S: AsRef<Path>, U: AsRef<str>, R: CommandRunner> {
socket_path: S,
service_name: &'a str,
command_runner: SetuidCommandRunner<'a, U, R>,
}
impl<S: AsRef<Path>, U: AsRef<str>, R: CommandRunner> UserService<'static, S, U, R> {
pub fn new(
socket_path: S,
user_name: U,
service_name: &'static str,
command_runner: &'static R,
) -> Self {
Self {
socket_path,
service_name,
command_runner: SetuidCommandRunner::new(user_name, command_runner),
}
}
}
impl<S: AsRef<Path>, U: AsRef<str>, R: CommandRunner> UserService<'_, S, U, R> {
async fn systemctl_wait_for_dbus(&self, args: &[&OsStr]) -> Result<String, Box<dyn Error>> {
let mut tries = 5;
loop {
let result = self.command_runner.run_with_args("systemctl", args).await?;
if result.status.success() {
return Ok(String::from_utf8(result.stdout)?.trim_end().to_string());
} else {
let raw_stderr = String::from_utf8(result.stderr)?;
let stderr = raw_stderr.trim_end();
if stderr != "Failed to connect to bus: No such file or directory" {
return Err(stderr.into());
}
}
tries -= 1;
if tries == 0 {
return Err("Gave up waiting for dbus to appear".to_string().into());
}
sleep(Duration::from_millis(500)).await;
}
}
async fn check_if_service(&self) -> Result<bool, Box<dyn Error>> {
loop {
let active_state = self
.systemctl_wait_for_dbus(args![
"--user",
"show",
"--property",
"ActiveState",
self.service_name,
])
.await?;
match active_state.as_ref() {
"ActiveState=activating" => sleep(Duration::from_millis(500)).await,
"ActiveState=active" => return Ok(true),
"ActiveState=failed" => {
return Err(
String::from_utf8(
self
.command_runner
.get_output(
"journalctl",
args!["--user", format!("--user-unit={}", self.service_name)],
)
.await?,
)?
.into(),
)
}
_ => return Ok(false),
}
}
}
}
#[async_trait(?Send)]
impl<S: AsRef<Path>, U: AsRef<str>, R: CommandRunner> Symbol for UserService<'_, S, U, R> {
async fn target_reached(&self) -> Result<bool, Box<dyn Error>> {
self.check_if_service().await
}
async fn execute(&self) -> Result<(), Box<dyn Error>> {
self
.systemctl_wait_for_dbus(args!["--user", "enable", self.service_name])
.await?;
self
.systemctl_wait_for_dbus(args!["--user", "restart", self.service_name])
.await?;
loop {
if !(self.check_if_service().await?) {
return Err("Generic error".into());
}
if self.socket_path.as_ref().exists() {
return Ok(());
}
sleep(Duration::from_millis(500)).await;
}
}
}