use std::sync::mpsc;
use std::thread;
use std::time::Duration;
use common::{Error, Result};
use zbus::blocking::Connection;
const DESTINATION: &str = "org.freedesktop.systemd1";
const PATH: &str = "/org/freedesktop/systemd1";
const INTERFACE: &str = "org.freedesktop.systemd1.Manager";
pub struct SystemdUser {
conn: Connection,
}
impl SystemdUser {
pub fn connect() -> Option<Self> {
Connection::session().ok().map(|conn| Self { conn })
}
pub fn freeze_unit(&self, unit: &str, timeout: Duration) -> Result<()> {
let unit = unit.to_string();
self.call_with_timeout(timeout, move |conn| {
conn.call_method(
Some(DESTINATION),
PATH,
Some(INTERFACE),
"FreezeUnit",
&(unit.as_str(),),
)
.map(|_| ())
.map_err(|e| Error::Cgroup(format!("FreezeUnit({unit}) failed: {e}")))
})
}
pub fn thaw_unit(&self, unit: &str, timeout: Duration) -> Result<()> {
let unit = unit.to_string();
self.call_with_timeout(timeout, move |conn| {
conn.call_method(
Some(DESTINATION),
PATH,
Some(INTERFACE),
"ThawUnit",
&(unit.as_str(),),
)
.map(|_| ())
.map_err(|e| Error::Cgroup(format!("ThawUnit({unit}) failed: {e}")))
})
}
fn call_with_timeout(
&self,
timeout: Duration,
f: impl FnOnce(&Connection) -> Result<()> + Send + 'static,
) -> Result<()> {
let conn = self.conn.clone();
run_with_timeout(timeout, move || f(&conn))
}
}
fn run_with_timeout<T: Send + 'static>(
timeout: Duration,
f: impl FnOnce() -> Result<T> + Send + 'static,
) -> Result<T> {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let _ = tx.send(f());
});
rx.recv_timeout(timeout).unwrap_or_else(|_| {
Err(Error::Cgroup(format!(
"D-Bus call timed out after {timeout:?}"
)))
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn run_with_timeout_returns_err_on_timeout() {
let result: Result<()> = run_with_timeout(Duration::from_millis(50), || {
thread::sleep(Duration::from_secs(5));
Ok(())
});
assert!(result.is_err(), "expected a timeout error");
}
#[test]
fn run_with_timeout_returns_ok_when_fast() {
let result: Result<u32> = run_with_timeout(Duration::from_secs(2), || Ok(42));
assert_eq!(result.unwrap(), 42);
}
#[test]
#[ignore = "requires a session bus and a running user unit; run manually"]
fn freeze_thaw_transient_unit_roundtrip() {
let s = SystemdUser::connect().expect("session bus");
s.freeze_unit("rlm-dbus-test.service", Duration::from_secs(2))
.expect("freeze");
s.thaw_unit("rlm-dbus-test.service", Duration::from_secs(2))
.expect("thaw");
}
}