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
107 if let Some(tilde_pos) = no_v_version.find('~') {
110 let major = no_v_version[..tilde_pos].parse::<u32>()?;
111 let after_tilde = &no_v_version[tilde_pos + 1..];
112 let mut tilde_parts = after_tilde.splitn(2, '.');
113 let minor = tilde_parts.next().unwrap_or("").to_string();
114 let os = tilde_parts.next().unwrap_or("").to_string();
115 return Ok(SystemdVersion {
116 major,
117 minor,
118 revision: None,
119 os,
120 });
121 }
122
123 let mut parts = no_v_version.split('.');
124 let split_count = parts.clone().count();
125 let major = parts
126 .next()
127 .ok_or_else(|| MonitordSystemError::VersionParseError("No valid major version".into()))?
128 .parse::<u32>()?;
129 let minor = parts
130 .next()
131 .ok_or_else(|| MonitordSystemError::VersionParseError("No valid minor version".into()))?
132 .to_string();
133 let mut revision = None;
134 if split_count > 3 {
135 revision = parts.next().and_then(|s| s.parse::<u32>().ok());
136 }
137 let os = parts.collect::<Vec<&str>>().join(".");
138 Ok(SystemdVersion {
139 major,
140 minor,
141 revision,
142 os,
143 })
144 }
145}
146
147pub async fn get_system_state(
149 connection: &zbus::Connection,
150) -> Result<SystemdSystemState, MonitordSystemError> {
151 let p = crate::dbus::zbus_systemd::ManagerProxy::builder(connection)
152 .cache_properties(zbus::proxy::CacheProperties::No)
153 .build()
154 .await
155 .map_err(MonitordSystemError::ZbusError)?;
156
157 let state = match p.system_state().await {
158 Ok(system_state) => match system_state.as_str() {
159 "initializing" => crate::system::SystemdSystemState::initializing,
160 "starting" => crate::system::SystemdSystemState::starting,
161 "running" => crate::system::SystemdSystemState::running,
162 "degraded" => crate::system::SystemdSystemState::degraded,
163 "maintenance" => crate::system::SystemdSystemState::maintenance,
164 "stopping" => crate::system::SystemdSystemState::stopping,
165 "offline" => crate::system::SystemdSystemState::offline,
166 _ => crate::system::SystemdSystemState::unknown,
167 },
168 Err(err) => {
169 error!("Failed to get system-state: {:?}", err);
170 crate::system::SystemdSystemState::unknown
171 }
172 };
173 Ok(state)
174}
175
176pub async fn update_system_stats(
178 connection: zbus::Connection,
179 locked_machine_stats: Arc<RwLock<MachineStats>>,
180) -> anyhow::Result<()> {
181 let mut machine_stats = locked_machine_stats.write().await;
182 machine_stats.system_state = crate::system::get_system_state(&connection)
183 .await
184 .map_err(|e| anyhow::anyhow!("Error getting system state: {:?}", e))?;
185 Ok(())
186}
187
188pub async fn get_version(
189 connection: &zbus::Connection,
190) -> Result<SystemdVersion, MonitordSystemError> {
191 let p = crate::dbus::zbus_systemd::ManagerProxy::builder(connection)
192 .cache_properties(zbus::proxy::CacheProperties::No)
193 .build()
194 .await
195 .map_err(MonitordSystemError::ZbusError)?;
196 let version_string = p.version().await?;
197 version_string.try_into()
198}
199
200pub async fn update_version(
202 connection: zbus::Connection,
203 locked_machine_stats: Arc<RwLock<MachineStats>>,
204) -> anyhow::Result<()> {
205 let mut machine_stats = locked_machine_stats.write().await;
206 machine_stats.version = crate::system::get_version(&connection)
207 .await
208 .map_err(|e| anyhow::anyhow!("Error getting systemd version: {:?}", e))?;
209 Ok(())
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215
216 #[test]
217 fn test_display_struct() {
218 assert_eq!(
219 format!("{}", SystemdSystemState::running),
220 String::from("running"),
221 )
222 }
223
224 #[test]
225 fn test_parsing_systemd_versions() -> Result<(), MonitordSystemError> {
226 let parsed: SystemdVersion = "969.1.69.fc69".to_string().try_into()?;
227 assert_eq!(
228 SystemdVersion::new(969, String::from("1"), Some(69), String::from("fc69")),
229 parsed
230 );
231
232 let parsed: SystemdVersion = "969.1.fc69".to_string().try_into()?;
234 assert_eq!(
235 SystemdVersion::new(969, String::from("1"), None, String::from("fc69")),
236 parsed
237 );
238
239 let parsed: SystemdVersion = String::from("969.6-9.9.hs+fb.el9").try_into()?;
241 assert_eq!(
242 SystemdVersion::new(969, String::from("6-9"), Some(9), String::from("hs+fb.el9")),
243 parsed
244 );
245
246 let parsed: SystemdVersion = String::from("v299.6-9.9.hs+fb.el9").try_into()?;
247 assert_eq!(
248 SystemdVersion::new(299, String::from("6-9"), Some(9), String::from("hs+fb.el9")),
249 parsed
250 );
251
252 let parsed: SystemdVersion = String::from("260~rc1-5.fc45").try_into()?;
254 assert_eq!(
255 SystemdVersion::new(260, String::from("rc1-5"), None, String::from("fc45")),
256 parsed
257 );
258
259 Ok(())
260 }
261}