use std::convert::TryInto;
use std::fmt;
use std::sync::Arc;
use anyhow::anyhow;
use anyhow::Context;
use int_enum::IntEnum;
use serde_repr::Deserialize_repr;
use serde_repr::Serialize_repr;
use strum_macros::EnumIter;
use strum_macros::EnumString;
use thiserror::Error;
use tokio::sync::RwLock;
use tracing::error;
use crate::MachineStats;
#[derive(Error, Debug)]
pub enum MonitordSystemError {
#[error("monitord::system failed: {0:#}")]
GenericError(#[from] anyhow::Error),
#[error("Unable to connect to DBUS via zbus: {0:#}")]
ZbusError(#[from] zbus::Error),
}
#[allow(non_camel_case_types)]
#[derive(
Serialize_repr,
Deserialize_repr,
Clone,
Copy,
Debug,
Default,
Eq,
PartialEq,
EnumIter,
EnumString,
IntEnum,
strum_macros::Display,
)]
#[repr(u8)]
pub enum SystemdSystemState {
#[default]
unknown = 0,
initializing = 1,
starting = 2,
running = 3,
degraded = 4,
maintenance = 5,
stopping = 6,
offline = 7,
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Default, Eq, PartialEq)]
pub struct SystemdVersion {
major: u32,
minor: String,
revision: Option<u32>,
os: String,
}
impl SystemdVersion {
pub fn new(major: u32, minor: String, revision: Option<u32>, os: String) -> SystemdVersion {
Self {
major,
minor,
revision,
os,
}
}
}
impl fmt::Display for SystemdVersion {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(revision) = self.revision {
return write!(f, "{}.{}.{}.{}", self.major, self.minor, revision, self.os);
}
write!(f, "{}.{}.{}", self.major, self.minor, self.os)
}
}
impl TryFrom<String> for SystemdVersion {
type Error = MonitordSystemError;
fn try_from(s: String) -> Result<Self, Self::Error> {
let no_v_version = if let Some(stripped_v) = s.strip_prefix('v') {
stripped_v.to_string()
} else {
s.clone()
};
let mut parts = no_v_version.split('.');
let split_count = parts.clone().count();
let major = parts
.next()
.with_context(|| "No valid major version")?
.parse::<u32>()
.with_context(|| format!("Failed to parse major version: {:?}", s))?;
let minor = parts
.next()
.with_context(|| "No valid minor version")?
.parse::<String>()
.with_context(|| format!("Failed to parse minor version: {:?}", s))?;
let mut revision = None;
if split_count > 3 {
revision = parts.next().and_then(|s| s.parse::<u32>().ok());
}
let remaining_elements: Vec<&str> = parts.collect();
let os = remaining_elements.join(".").to_string();
Ok(SystemdVersion {
major,
minor,
revision,
os,
})
}
}
pub async fn get_system_state(
connection: &zbus::Connection,
) -> Result<SystemdSystemState, MonitordSystemError> {
let p = crate::dbus::zbus_systemd::ManagerProxy::new(connection)
.await
.map_err(MonitordSystemError::ZbusError)?;
let state = match p.system_state().await {
Ok(system_state) => match system_state.as_str() {
"initializing" => crate::system::SystemdSystemState::initializing,
"starting" => crate::system::SystemdSystemState::starting,
"running" => crate::system::SystemdSystemState::running,
"degraded" => crate::system::SystemdSystemState::degraded,
"maintenance" => crate::system::SystemdSystemState::maintenance,
"stopping" => crate::system::SystemdSystemState::stopping,
"offline" => crate::system::SystemdSystemState::offline,
_ => crate::system::SystemdSystemState::unknown,
},
Err(err) => {
error!("Failed to get system-state: {:?}", err);
crate::system::SystemdSystemState::unknown
}
};
Ok(state)
}
pub async fn update_system_stats(
connection: zbus::Connection,
locked_machine_stats: Arc<RwLock<MachineStats>>,
) -> anyhow::Result<()> {
let mut machine_stats = locked_machine_stats.write().await;
machine_stats.system_state = crate::system::get_system_state(&connection)
.await
.map_err(|e| anyhow!("Error getting system state: {:?}", e))?;
Ok(())
}
pub async fn get_version(
connection: &zbus::Connection,
) -> Result<SystemdVersion, MonitordSystemError> {
let p = crate::dbus::zbus_systemd::ManagerProxy::new(connection)
.await
.map_err(MonitordSystemError::ZbusError)?;
let version_string = p
.version()
.await
.with_context(|| "Unable to get systemd version string".to_string())?;
version_string.try_into()
}
pub async fn update_version(
connection: zbus::Connection,
locked_machine_stats: Arc<RwLock<MachineStats>>,
) -> anyhow::Result<()> {
let mut machine_stats = locked_machine_stats.write().await;
machine_stats.version = crate::system::get_version(&connection)
.await
.map_err(|e| anyhow!("Error getting systemd version: {:?}", e))?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use anyhow::Result;
#[test]
fn test_display_struct() {
assert_eq!(
format!("{}", SystemdSystemState::running),
String::from("running"),
)
}
#[test]
fn test_parsing_systemd_versions() -> Result<()> {
let parsed: SystemdVersion = "969.1.69.fc69".to_string().try_into()?;
assert_eq!(
SystemdVersion::new(969, String::from("1"), Some(69), String::from("fc69")),
parsed
);
let parsed: SystemdVersion = "969.1.fc69".to_string().try_into()?;
assert_eq!(
SystemdVersion::new(969, String::from("1"), None, String::from("fc69")),
parsed
);
let parsed: SystemdVersion = String::from("969.6-9.9.hs+fb.el9").try_into()?;
assert_eq!(
SystemdVersion::new(969, String::from("6-9"), Some(9), String::from("hs+fb.el9")),
parsed
);
let parsed: SystemdVersion = String::from("v299.6-9.9.hs+fb.el9").try_into()?;
assert_eq!(
SystemdVersion::new(299, String::from("6-9"), Some(9), String::from("hs+fb.el9")),
parsed
);
Ok(())
}
}