monitord/
system.rs

1//! # system module
2//!
3//! Handle systemd's overall "system" state. Basically says if we've successfully
4//! booted, stated all units or have been asked to stop, be offline etc.
5
6use std::convert::TryInto;
7use std::fmt;
8use std::sync::Arc;
9
10use anyhow::anyhow;
11use anyhow::Context;
12use int_enum::IntEnum;
13use serde_repr::Deserialize_repr;
14use serde_repr::Serialize_repr;
15use strum_macros::EnumIter;
16use strum_macros::EnumString;
17use thiserror::Error;
18use tokio::sync::RwLock;
19use tracing::error;
20
21use crate::MachineStats;
22
23#[derive(Error, Debug)]
24pub enum MonitordSystemError {
25    #[error("monitord::system failed: {0:#}")]
26    GenericError(#[from] anyhow::Error),
27    #[error("Unable to connect to DBUS via zbus: {0:#}")]
28    ZbusError(#[from] zbus::Error),
29}
30
31#[allow(non_camel_case_types)]
32#[derive(
33    Serialize_repr,
34    Deserialize_repr,
35    Clone,
36    Copy,
37    Debug,
38    Default,
39    Eq,
40    PartialEq,
41    EnumIter,
42    EnumString,
43    IntEnum,
44    strum_macros::Display,
45)]
46#[repr(u8)]
47pub enum SystemdSystemState {
48    #[default]
49    unknown = 0,
50    initializing = 1,
51    starting = 2,
52    running = 3,
53    degraded = 4,
54    maintenance = 5,
55    stopping = 6,
56    offline = 7,
57}
58
59#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Default, Eq, PartialEq)]
60pub struct SystemdVersion {
61    major: u32,
62    minor: String,
63    revision: Option<u32>,
64    os: String,
65}
66impl SystemdVersion {
67    pub fn new(major: u32, minor: String, revision: Option<u32>, os: String) -> SystemdVersion {
68        Self {
69            major,
70            minor,
71            revision,
72            os,
73        }
74    }
75}
76impl fmt::Display for SystemdVersion {
77    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
78        if let Some(revision) = self.revision {
79            return write!(f, "{}.{}.{}.{}", self.major, self.minor, revision, self.os);
80        }
81        write!(f, "{}.{}.{}", self.major, self.minor, self.os)
82    }
83}
84impl TryFrom<String> for SystemdVersion {
85    type Error = MonitordSystemError;
86
87    fn try_from(s: String) -> Result<Self, Self::Error> {
88        let no_v_version = if let Some(stripped_v) = s.strip_prefix('v') {
89            stripped_v.to_string()
90        } else {
91            s.clone()
92        };
93        let mut parts = no_v_version.split('.');
94        let split_count = parts.clone().count();
95        let major = parts
96            .next()
97            .with_context(|| "No valid major version")?
98            .parse::<u32>()
99            .with_context(|| format!("Failed to parse major version: {:?}", s))?;
100        let minor = parts
101            .next()
102            .with_context(|| "No valid minor version")?
103            .parse::<String>()
104            .with_context(|| format!("Failed to parse minor version: {:?}", s))?;
105        let mut revision = None;
106        if split_count > 3 {
107            revision = parts.next().and_then(|s| s.parse::<u32>().ok());
108        }
109        let remaining_elements: Vec<&str> = parts.collect();
110        let os = remaining_elements.join(".").to_string();
111        Ok(SystemdVersion {
112            major,
113            minor,
114            revision,
115            os,
116        })
117    }
118}
119
120//pub fn get_system_state(dbus_address: &str) -> Result<SystemdSystemState, dbus::Error> {
121pub async fn get_system_state(
122    connection: &zbus::Connection,
123) -> Result<SystemdSystemState, MonitordSystemError> {
124    let p = crate::dbus::zbus_systemd::ManagerProxy::new(connection)
125        .await
126        .map_err(MonitordSystemError::ZbusError)?;
127
128    let state = match p.system_state().await {
129        Ok(system_state) => match system_state.as_str() {
130            "initializing" => crate::system::SystemdSystemState::initializing,
131            "starting" => crate::system::SystemdSystemState::starting,
132            "running" => crate::system::SystemdSystemState::running,
133            "degraded" => crate::system::SystemdSystemState::degraded,
134            "maintenance" => crate::system::SystemdSystemState::maintenance,
135            "stopping" => crate::system::SystemdSystemState::stopping,
136            "offline" => crate::system::SystemdSystemState::offline,
137            _ => crate::system::SystemdSystemState::unknown,
138        },
139        Err(err) => {
140            error!("Failed to get system-state: {:?}", err);
141            crate::system::SystemdSystemState::unknown
142        }
143    };
144    Ok(state)
145}
146
147/// Async wrapper than can update system stats when passed a locked struct
148pub async fn update_system_stats(
149    connection: zbus::Connection,
150    locked_machine_stats: Arc<RwLock<MachineStats>>,
151) -> anyhow::Result<()> {
152    let mut machine_stats = locked_machine_stats.write().await;
153    machine_stats.system_state = crate::system::get_system_state(&connection)
154        .await
155        .map_err(|e| anyhow!("Error getting system state: {:?}", e))?;
156    Ok(())
157}
158
159pub async fn get_version(
160    connection: &zbus::Connection,
161) -> Result<SystemdVersion, MonitordSystemError> {
162    let p = crate::dbus::zbus_systemd::ManagerProxy::new(connection)
163        .await
164        .map_err(MonitordSystemError::ZbusError)?;
165    let version_string = p
166        .version()
167        .await
168        .with_context(|| "Unable to get systemd version string".to_string())?;
169    version_string.try_into()
170}
171
172/// Async wrapper than can update system stats when passed a locked struct
173pub async fn update_version(
174    connection: zbus::Connection,
175    locked_machine_stats: Arc<RwLock<MachineStats>>,
176) -> anyhow::Result<()> {
177    let mut machine_stats = locked_machine_stats.write().await;
178    machine_stats.version = crate::system::get_version(&connection)
179        .await
180        .map_err(|e| anyhow!("Error getting systemd version: {:?}", e))?;
181    Ok(())
182}
183
184#[cfg(test)]
185mod tests {
186    use super::*;
187    use anyhow::Result;
188
189    #[test]
190    fn test_display_struct() {
191        assert_eq!(
192            format!("{}", SystemdSystemState::running),
193            String::from("running"),
194        )
195    }
196
197    #[test]
198    fn test_parsing_systemd_versions() -> Result<()> {
199        let parsed: SystemdVersion = "969.1.69.fc69".to_string().try_into()?;
200        assert_eq!(
201            SystemdVersion::new(969, String::from("1"), Some(69), String::from("fc69")),
202            parsed
203        );
204
205        // No revision
206        let parsed: SystemdVersion = "969.1.fc69".to_string().try_into()?;
207        assert_eq!(
208            SystemdVersion::new(969, String::from("1"), None, String::from("fc69")),
209            parsed
210        );
211
212        // #bigCompany strings
213        let parsed: SystemdVersion = String::from("969.6-9.9.hs+fb.el9").try_into()?;
214        assert_eq!(
215            SystemdVersion::new(969, String::from("6-9"), Some(9), String::from("hs+fb.el9")),
216            parsed
217        );
218
219        let parsed: SystemdVersion = String::from("v299.6-9.9.hs+fb.el9").try_into()?;
220        assert_eq!(
221            SystemdVersion::new(299, String::from("6-9"), Some(9), String::from("hs+fb.el9")),
222            parsed
223        );
224
225        Ok(())
226    }
227}