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