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)]
74pub struct SystemdVersion {
75 major: u32,
77 minor: String,
79 revision: Option<u32>,
81 os: String,
83}
84impl SystemdVersion {
85 pub fn new(major: u32, minor: String, revision: Option<u32>, os: String) -> SystemdVersion {
86 Self {
87 major,
88 minor,
89 revision,
90 os,
91 }
92 }
93}
94impl fmt::Display for SystemdVersion {
95 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
96 if let Some(revision) = self.revision {
97 return write!(f, "{}.{}.{}.{}", self.major, self.minor, revision, self.os);
98 }
99 write!(f, "{}.{}.{}", self.major, self.minor, self.os)
100 }
101}
102impl TryFrom<String> for SystemdVersion {
103 type Error = MonitordSystemError;
104
105 fn try_from(s: String) -> Result<Self, Self::Error> {
106 let no_v_version = s.strip_prefix('v').unwrap_or(&s);
107
108 if let Some(tilde_pos) = no_v_version.find('~') {
111 let major = no_v_version[..tilde_pos].parse::<u32>()?;
112 let after_tilde = &no_v_version[tilde_pos + 1..];
113 let mut tilde_parts = after_tilde.splitn(2, '.');
114 let minor = tilde_parts.next().unwrap_or("").to_string();
115 let os = tilde_parts.next().unwrap_or("").to_string();
116 return Ok(SystemdVersion {
117 major,
118 minor,
119 revision: None,
120 os,
121 });
122 }
123
124 let mut parts = no_v_version.split('.');
125 let split_count = parts.clone().count();
126 let major = parts
127 .next()
128 .ok_or_else(|| MonitordSystemError::VersionParseError("No valid major version".into()))?
129 .parse::<u32>()?;
130 let minor = parts
131 .next()
132 .ok_or_else(|| MonitordSystemError::VersionParseError("No valid minor version".into()))?
133 .to_string();
134 let mut revision = None;
135 if split_count > 3 {
136 revision = parts.next().and_then(|s| s.parse::<u32>().ok());
137 }
138 let os = parts.collect::<Vec<&str>>().join(".");
139 Ok(SystemdVersion {
140 major,
141 minor,
142 revision,
143 os,
144 })
145 }
146}
147
148pub async fn get_system_state(
150 connection: &zbus::Connection,
151) -> Result<SystemdSystemState, MonitordSystemError> {
152 let p = crate::dbus::zbus_systemd::ManagerProxy::builder(connection)
153 .cache_properties(zbus::proxy::CacheProperties::No)
154 .build()
155 .await
156 .map_err(MonitordSystemError::ZbusError)?;
157
158 let state = match p.system_state().await {
159 Ok(system_state) => match system_state.as_str() {
160 "initializing" => crate::system::SystemdSystemState::initializing,
161 "starting" => crate::system::SystemdSystemState::starting,
162 "running" => crate::system::SystemdSystemState::running,
163 "degraded" => crate::system::SystemdSystemState::degraded,
164 "maintenance" => crate::system::SystemdSystemState::maintenance,
165 "stopping" => crate::system::SystemdSystemState::stopping,
166 "offline" => crate::system::SystemdSystemState::offline,
167 _ => crate::system::SystemdSystemState::unknown,
168 },
169 Err(err) => {
170 error!("Failed to get system-state: {:?}", err);
171 crate::system::SystemdSystemState::unknown
172 }
173 };
174 Ok(state)
175}
176
177pub async fn update_system_stats(
184 connection: zbus::Connection,
185 locked_machine_stats: Arc<RwLock<MachineStats>>,
186) -> anyhow::Result<()> {
187 let system_state = crate::system::get_system_state(&connection)
188 .instrument(tracing::debug_span!("system_state_dbus_call"))
189 .await
190 .map_err(|e| anyhow::anyhow!("Error getting system state: {:?}", e))?;
191 let mut machine_stats = locked_machine_stats
192 .write()
193 .instrument(tracing::debug_span!("system_state_acquire_write_lock"))
194 .await;
195 machine_stats.system_state = system_state;
196 Ok(())
197}
198
199pub async fn get_version(
200 connection: &zbus::Connection,
201) -> Result<SystemdVersion, MonitordSystemError> {
202 let p = crate::dbus::zbus_systemd::ManagerProxy::builder(connection)
203 .cache_properties(zbus::proxy::CacheProperties::No)
204 .build()
205 .await
206 .map_err(MonitordSystemError::ZbusError)?;
207 let version_string = p.version().await?;
208 version_string.try_into()
209}
210
211pub async fn update_version(
216 connection: zbus::Connection,
217 locked_machine_stats: Arc<RwLock<MachineStats>>,
218) -> anyhow::Result<()> {
219 let version = crate::system::get_version(&connection)
220 .instrument(tracing::debug_span!("version_dbus_call"))
221 .await
222 .map_err(|e| anyhow::anyhow!("Error getting systemd version: {:?}", e))?;
223 let mut machine_stats = locked_machine_stats
224 .write()
225 .instrument(tracing::debug_span!("version_acquire_write_lock"))
226 .await;
227 machine_stats.version = version;
228 Ok(())
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234
235 #[test]
236 fn test_display_struct() {
237 assert_eq!(
238 format!("{}", SystemdSystemState::running),
239 String::from("running"),
240 )
241 }
242
243 #[test]
244 fn test_parsing_systemd_versions() -> Result<(), MonitordSystemError> {
245 let parsed: SystemdVersion = "969.1.69.fc69".to_string().try_into()?;
246 assert_eq!(
247 SystemdVersion::new(969, String::from("1"), Some(69), String::from("fc69")),
248 parsed
249 );
250
251 let parsed: SystemdVersion = "969.1.fc69".to_string().try_into()?;
253 assert_eq!(
254 SystemdVersion::new(969, String::from("1"), None, String::from("fc69")),
255 parsed
256 );
257
258 let parsed: SystemdVersion = String::from("969.6-9.9.hs+fb.el9").try_into()?;
260 assert_eq!(
261 SystemdVersion::new(969, String::from("6-9"), Some(9), String::from("hs+fb.el9")),
262 parsed
263 );
264
265 let parsed: SystemdVersion = String::from("v299.6-9.9.hs+fb.el9").try_into()?;
266 assert_eq!(
267 SystemdVersion::new(299, String::from("6-9"), Some(9), String::from("hs+fb.el9")),
268 parsed
269 );
270
271 let parsed: SystemdVersion = String::from("260~rc1-5.fc45").try_into()?;
273 assert_eq!(
274 SystemdVersion::new(260, String::from("rc1-5"), None, String::from("fc45")),
275 parsed
276 );
277
278 Ok(())
279 }
280}