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;
18use tracing::Instrument;
19
20use crate::MachineStats;
21
22#[derive(Error, Debug)]
23pub enum MonitordSystemError {
24 #[error("Unable to connect to DBUS via zbus: {0:#}")]
25 ZbusError(#[from] zbus::Error),
26 #[error("Version parse error: {0}")]
27 VersionParseError(String),
28 #[error("Integer parse error: {0}")]
29 IntParseError(#[from] std::num::ParseIntError),
30}
31
32#[allow(non_camel_case_types)]
36#[derive(
37 Serialize_repr,
38 Deserialize_repr,
39 Clone,
40 Copy,
41 Debug,
42 Default,
43 Eq,
44 PartialEq,
45 EnumIter,
46 EnumString,
47 IntEnum,
48 strum_macros::Display,
49)]
50#[repr(u8)]
51pub enum SystemdSystemState {
52 #[default]
54 unknown = 0,
55 initializing = 1,
57 starting = 2,
59 running = 3,
61 degraded = 4,
63 maintenance = 5,
65 stopping = 6,
67 offline = 7,
69}
70
71#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Default, Eq, PartialEq)]
89pub struct SystemdVersion {
90 major: u32,
92 minor: String,
94 revision: Option<u32>,
96 os: String,
98}
99impl SystemdVersion {
100 pub fn new(major: u32, minor: String, revision: Option<u32>, os: String) -> SystemdVersion {
101 Self {
102 major,
103 minor,
104 revision,
105 os,
106 }
107 }
108}
109impl fmt::Display for SystemdVersion {
110 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
115 write!(f, "{}", self.major)?;
116 if !self.minor.is_empty() {
117 write!(f, ".{}", self.minor)?;
118 }
119 if let Some(revision) = self.revision {
120 write!(f, ".{revision}")?;
121 }
122 if !self.os.is_empty() {
123 write!(f, ".{}", self.os)?;
124 }
125 Ok(())
126 }
127}
128impl TryFrom<String> for SystemdVersion {
129 type Error = MonitordSystemError;
130
131 fn try_from(s: String) -> Result<Self, Self::Error> {
132 let no_v_version = s.strip_prefix('v').unwrap_or(&s);
133
134 let first_dot = no_v_version.find('.').unwrap_or(no_v_version.len());
140 if let Some(sep_pos) = no_v_version[..first_dot].find(['~', '-']) {
141 let major = no_v_version[..sep_pos].parse::<u32>()?;
142 let after_sep = &no_v_version[sep_pos + 1..];
143 let mut tilde_parts = after_sep.splitn(2, '.');
144 let minor = tilde_parts.next().unwrap_or("").to_string();
145 let os = tilde_parts.next().unwrap_or("").to_string();
146 return Ok(SystemdVersion {
147 major,
148 minor,
149 revision: None,
150 os,
151 });
152 }
153
154 let mut parts = no_v_version.split('.');
155 let split_count = parts.clone().count();
156 let major = parts
157 .next()
158 .ok_or_else(|| MonitordSystemError::VersionParseError("No valid major version".into()))?
159 .parse::<u32>()?;
160 let minor = parts.next().unwrap_or_default().to_string();
163 let mut revision = None;
164 if split_count > 3 {
165 revision = parts.next().and_then(|s| s.parse::<u32>().ok());
166 }
167 let os = parts.collect::<Vec<&str>>().join(".");
168 Ok(SystemdVersion {
169 major,
170 minor,
171 revision,
172 os,
173 })
174 }
175}
176
177pub fn parse_system_state(system_state: &str) -> SystemdSystemState {
182 match system_state {
183 "initializing" => SystemdSystemState::initializing,
184 "starting" => SystemdSystemState::starting,
185 "running" => SystemdSystemState::running,
186 "degraded" => SystemdSystemState::degraded,
187 "maintenance" => SystemdSystemState::maintenance,
188 "stopping" => SystemdSystemState::stopping,
189 "offline" => SystemdSystemState::offline,
190 _ => SystemdSystemState::unknown,
191 }
192}
193
194pub async fn get_system_state(
196 connection: &zbus::Connection,
197) -> Result<SystemdSystemState, MonitordSystemError> {
198 let p = crate::dbus::zbus_systemd::ManagerProxy::builder(connection)
199 .cache_properties(zbus::proxy::CacheProperties::No)
200 .build()
201 .await
202 .map_err(MonitordSystemError::ZbusError)?;
203
204 let state = match p.system_state().await {
205 Ok(system_state) => parse_system_state(&system_state),
206 Err(err) => {
207 error!("Failed to get system-state: {:?}", err);
208 crate::system::SystemdSystemState::unknown
209 }
210 };
211 Ok(state)
212}
213
214pub async fn update_system_stats(
221 connection: zbus::Connection,
222 locked_machine_stats: Arc<RwLock<MachineStats>>,
223) -> anyhow::Result<()> {
224 let system_state = crate::system::get_system_state(&connection)
225 .instrument(tracing::debug_span!("system_state_dbus_call"))
226 .await
227 .map_err(|e| anyhow::anyhow!("Error getting system state: {:?}", e))?;
228 let mut machine_stats = locked_machine_stats
229 .write()
230 .instrument(tracing::debug_span!("system_state_acquire_write_lock"))
231 .await;
232 machine_stats.system_state = system_state;
233 Ok(())
234}
235
236pub async fn get_version(
237 connection: &zbus::Connection,
238) -> Result<SystemdVersion, MonitordSystemError> {
239 let p = crate::dbus::zbus_systemd::ManagerProxy::builder(connection)
240 .cache_properties(zbus::proxy::CacheProperties::No)
241 .build()
242 .await
243 .map_err(MonitordSystemError::ZbusError)?;
244 let version_string = p.version().await?;
245 version_string.try_into()
246}
247
248pub async fn update_version(
253 connection: zbus::Connection,
254 locked_machine_stats: Arc<RwLock<MachineStats>>,
255) -> anyhow::Result<()> {
256 let version = crate::system::get_version(&connection)
257 .instrument(tracing::debug_span!("version_dbus_call"))
258 .await
259 .map_err(|e| anyhow::anyhow!("Error getting systemd version: {:?}", e))?;
260 let mut machine_stats = locked_machine_stats
261 .write()
262 .instrument(tracing::debug_span!("version_acquire_write_lock"))
263 .await;
264 machine_stats.version = version;
265 Ok(())
266}
267
268#[cfg(test)]
269mod tests {
270 use super::*;
271
272 #[test]
273 fn test_display_struct() {
274 assert_eq!(
275 format!("{}", SystemdSystemState::running),
276 String::from("running"),
277 )
278 }
279
280 #[test]
281 fn test_parse_system_state() {
282 assert_eq!(parse_system_state("running"), SystemdSystemState::running);
283 assert_eq!(parse_system_state("degraded"), SystemdSystemState::degraded);
284 assert_eq!(parse_system_state("brand-new"), SystemdSystemState::unknown);
286 }
287
288 #[test]
289 fn test_parsing_systemd_versions() -> Result<(), MonitordSystemError> {
290 let parsed: SystemdVersion = "969.1.69.fc69".to_string().try_into()?;
291 assert_eq!(
292 SystemdVersion::new(969, String::from("1"), Some(69), String::from("fc69")),
293 parsed
294 );
295
296 let parsed: SystemdVersion = "969.1.fc69".to_string().try_into()?;
298 assert_eq!(
299 SystemdVersion::new(969, String::from("1"), None, String::from("fc69")),
300 parsed
301 );
302
303 let parsed: SystemdVersion = String::from("969.6-9.9.hs+fb.el9").try_into()?;
305 assert_eq!(
306 SystemdVersion::new(969, String::from("6-9"), Some(9), String::from("hs+fb.el9")),
307 parsed
308 );
309
310 let parsed: SystemdVersion = String::from("v299.6-9.9.hs+fb.el9").try_into()?;
311 assert_eq!(
312 SystemdVersion::new(299, String::from("6-9"), Some(9), String::from("hs+fb.el9")),
313 parsed
314 );
315
316 let parsed: SystemdVersion = String::from("260~rc1-5.fc45").try_into()?;
318 assert_eq!(
319 SystemdVersion::new(260, String::from("rc1-5"), None, String::from("fc45")),
320 parsed
321 );
322
323 let parsed: SystemdVersion = String::from("262-1.fc46").try_into()?;
327 assert_eq!(
328 SystemdVersion::new(262, String::from("1"), None, String::from("fc46")),
329 parsed
330 );
331
332 let parsed: SystemdVersion = String::from("262-1").try_into()?;
334 assert_eq!(
335 SystemdVersion::new(262, String::from("1"), None, String::new()),
336 parsed
337 );
338
339 let parsed: SystemdVersion = String::from("969.6-9.9.hs+fb.el9").try_into()?;
342 assert_eq!(
343 SystemdVersion::new(969, String::from("6-9"), Some(9), String::from("hs+fb.el9")),
344 parsed
345 );
346
347 let parsed: SystemdVersion = String::from("263").try_into()?;
349 assert_eq!(
350 SystemdVersion::new(263, String::new(), None, String::new()),
351 parsed
352 );
353 assert_eq!(parsed.to_string(), "263");
354
355 assert!(SystemdVersion::try_from(String::from("not-a-version")).is_err());
358 assert!(SystemdVersion::try_from(String::new()).is_err());
359
360 Ok(())
361 }
362}