systemd_connector/
properties.rs

1//! Access properties of systemd units via systemctl
2
3use std::{collections::HashMap, io, str::FromStr};
4
5use thiserror::Error;
6
7/// Use `systemctl show` to get properties of a systemd unit.
8pub fn properties(unit: &str) -> Result<SystemDProperties, PropertyParseError> {
9    let mut cmd = std::process::Command::new("systemctl");
10    cmd.arg("show");
11    cmd.arg(unit);
12
13    let output = cmd.output()?;
14
15    String::from_utf8(output.stdout).unwrap().parse()
16}
17
18/// The active state of a systemd unit
19#[derive(Debug, Clone, Copy)]
20pub enum ActiveState {
21    /// The service is active and responding
22    Active,
23
24    /// Systemd is reloading the service
25    Reloading,
26
27    /// The service is inactive
28    Inactive,
29
30    /// The service has failed
31    Failed,
32
33    /// The service is activating - it has started but is not yet active
34    Activating,
35
36    /// The service is deactivating - it has stopped but is not yet inactive
37    Deactivating,
38}
39
40/// Errors that can occur when parsing a systemd unit's properties
41#[derive(Debug, Error)]
42#[error("{0} is not a valid state")]
43pub struct StateParseError(String);
44
45impl FromStr for ActiveState {
46    type Err = StateParseError;
47    fn from_str(s: &str) -> Result<Self, Self::Err> {
48        use ActiveState::*;
49        match s {
50            "active" => Ok(Active),
51            "reloading" => Ok(Reloading),
52            "inactive" => Ok(Inactive),
53            "failed" => Ok(Failed),
54            "activating" => Ok(Activating),
55            "deactivating" => Ok(Deactivating),
56            _ => Err(StateParseError(s.into())),
57        }
58    }
59}
60
61/// Errors that can occur when trying to access systemd unit properties
62#[derive(Debug, Error)]
63pub enum PropertyParseError {
64    /// An invalid state was returned
65    #[error(transparent)]
66    State(#[from] StateParseError),
67
68    /// A variable is missing the '=' delimiter
69    #[error("Line {0} is missing the delimiter '='")]
70    MissingDelimeter(String),
71
72    /// An expected property is missing
73    #[error("Missing property {0}")]
74    MissingProperty(&'static str),
75
76    /// A command error occured running systemctl
77    #[error("Running systemctl: {0}")]
78    CommandError(#[from] io::Error),
79}
80
81/// A map of systemd properties
82#[derive(Debug, Clone)]
83pub struct SystemDProperties {
84    properties: HashMap<String, String>,
85    active: ActiveState,
86}
87
88impl SystemDProperties {
89    /// Get the active state of the systemd unit
90    pub fn state(&self) -> ActiveState {
91        self.active
92    }
93
94    /// Get a property of the systemd unit
95    pub fn property(&self, name: &str) -> Option<&str> {
96        self.properties.get(name).map(|s| s.as_str())
97    }
98}
99
100impl FromStr for SystemDProperties {
101    type Err = PropertyParseError;
102    fn from_str(s: &str) -> Result<Self, Self::Err> {
103        let mut properties = HashMap::new();
104
105        for line in s.lines() {
106            let (key, value) = line
107                .trim()
108                .split_once('=')
109                .ok_or_else(|| PropertyParseError::MissingDelimeter(line.into()))?;
110
111            properties.insert(key.to_owned(), value.to_owned());
112        }
113
114        let active = properties
115            .get("ActiveState")
116            .ok_or(PropertyParseError::MissingProperty("ActiveState"))?
117            .parse()?;
118
119        Ok(Self { properties, active })
120    }
121}