1use 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
120pub 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
147pub 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
172pub 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 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 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}