aboutsummaryrefslogtreecommitdiff
path: root/src/dbus.rs
blob: 8fd2302b17794cf4158fd07aef3608425817a9de (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
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<String, Arc<Connection>>, Option<Arc<Connection>>)> = 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<Vec<ServiceStatus>>;
    async fn get_unit(&self, unit_name: String) -> Result<OwnedObjectPath>;
}

async fn ensure_system_conn() -> Result<Arc<Connection>> {
    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<Arc<Connection>> {
    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<String> {
    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(", ")))
    }
}