use anyhow::{anyhow, Result}; use serde::{Deserialize, Serialize}; use zbus::{ proxy, zvariant::{OwnedObjectPath, Type}, Connection, connection::Builder, address::{Address, transport::{Unix, Transport, UnixSocket}}, }; use std::{sync::Arc, collections::{BTreeMap, btree_map::Entry::*}, path::PathBuf}; use tokio::sync::Mutex; static CONNECTIONS: Mutex<(BTreeMap>, Option>)> = Mutex::const_new((BTreeMap::new(), None)); #[derive(Debug, Type, Deserialize, Serialize)] struct ServiceStatus { name: String, description: String, loaded: String, active: String, substate: String, following_state: String, unit_path: OwnedObjectPath, job_id: u32, job_type: String, job_path: OwnedObjectPath, } #[proxy( interface = "org.freedesktop.systemd1.Manager", default_service = "org.freedesktop.systemd1", default_path = "/org/freedesktop/systemd1" )] trait Manager { async fn list_units(&self) -> Result>; async fn get_unit(&self, unit_name: String) -> Result; } async fn ensure_system_conn() -> Result> { let mut connections = CONNECTIONS.lock().await; match &connections.1 { Some(system_bus) => Ok(system_bus.clone()), None => { let system_bus = Arc::new(Connection::system().await?); connections.1 = Some(system_bus.clone()); Ok(system_bus) }, } } async fn ensure_user_conn(user: &str) -> Result> { let mut connections = CONNECTIONS.lock().await; match connections.0.entry(user.to_owned()) { Occupied(oe) => Ok(oe.get().clone()), Vacant(ve) => { let mut path = PathBuf::new(); path.push("/run/user"); path.push(format!("{}", users::get_user_by_name(user).ok_or(anyhow!("Couldn't find user"))?.uid())); path.push("bus"); let trans = Transport::Unix(Unix::new(UnixSocket::File(path))); let conn = Builder::address(Address::new(trans))?.build().await?; Ok(ve.insert(Arc::new(conn)).clone()) }, } } pub(crate) async fn check_systemd_all(user: Option<&str>) -> Result { let conn = match user { None => ensure_system_conn().await, Some(username) => ensure_user_conn(username).await, }?; let manager = ManagerProxy::new(&conn).await?; let (good, bad) = manager.list_units().await?.into_iter().fold((0, vec![]), |(old_good, mut old_bad), unit| { if matches!(unit.substate.as_str(), "active" | "inactive" | "plugged" | "mounted" | "dead" | "listening" | "running" | "exited" | "waiting" | "abandoned" | "elapsed") { (old_good + 1, old_bad) } else { old_bad.push(format!("{}: {}", unit.name, unit.substate)); (old_good, old_bad) } }); if bad.is_empty() { Ok(format!("{good} good services")) } else { Err(anyhow!("Bad services: {}", bad.join(", "))) } }