From 40272b14491670cdf3d1b96302998be981ebf626 Mon Sep 17 00:00:00 2001 From: Joseph Fox-Rabinovitz Date: Thu, 1 May 2025 11:06:56 -0500 Subject: [PATCH 1/6] add get_active_state() --- README.md | 3 +++ src/lib.rs | 46 ++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 69cf589..b75e0e7 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,9 @@ systemctl.list_units(Some("service"), Some("enabled"), None); // list all services starting with cron systemctl.list_units(Some("service"), None, Some("cron*")); + +// Check if a unit is active +systemctl.get_active_state("service"); ``` ## Unit structure diff --git a/src/lib.rs b/src/lib.rs index 7c91457..7f2c62c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -65,7 +65,7 @@ impl SystemCtl { Some(4) => { return Err(Error::new( ErrorKind::PermissionDenied, - "Missing Priviledges or Unit not found", + "Missing privileges or unit not found", )) }, // unknown errorcodes @@ -158,6 +158,18 @@ impl SystemCtl { Ok(status.trim_end().eq("active")) } + /// Returns active state of the given `unit` + pub fn get_active_state(&self, unit: &str) -> std::io::Result { + let status = self.systemctl_capture(vec!["is-active", unit])?; + ActiveState::from_str(&status.trim_end()).map_or( + Err(Error::new( + ErrorKind::InvalidData, + format!("Invalid status {}", status), + )), + |x| Ok(x), + ) + } + /// Isolates given unit, only self and its dependencies are /// now actively running pub fn isolate(&self, unit: &str) -> std::io::Result { @@ -366,7 +378,7 @@ impl SystemCtl { if let Some(line) = line.strip_prefix("Loaded: ") { // Match and get rid of "Loaded: " if let Some(line) = line.strip_prefix("loaded ") { - u.state = State::Loaded; + u.loaded_state = LoadedState::Loaded; let line = line.strip_prefix('(').unwrap(); let line = line.strip_suffix(')').unwrap(); let items: Vec<&str> = line.split(';').collect(); @@ -378,7 +390,7 @@ impl SystemCtl { u.preset = items[2].trim().ends_with("enabled"); } } else if line.starts_with("masked") { - u.state = State::Masked; + u.loaded_state = LoadedState::Masked; } } else if let Some(line) = line.strip_prefix("Transient: ") { if line == "yes" { @@ -544,10 +556,10 @@ pub enum Type { Swap, } -/// `State` describes a Unit current state +/// `LoadedState` describes a Unit's current loaded state #[derive(Copy, Clone, PartialEq, Eq, EnumString, Debug, Default)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum State { +pub enum LoadedState { #[strum(serialize = "masked")] #[default] Masked, @@ -555,6 +567,24 @@ pub enum State { Loaded, } +/// `ActiveState` describes a Unit's current active state +#[derive(Copy, Clone, PartialEq, Eq, EnumString, Debug, Default)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(not(feature = "serde"), derive(strum_macros::Display))] +pub enum ActiveState { + #[default] + #[strum(serialize = "inactive", to_string = "Inactive")] + Inactive, + #[strum(serialize = "active", to_string = "Active")] + Active, + #[strum(serialize = "activating", to_string = "Activating")] + Activating, + #[strum(serialize = "deactivating", to_string = "Deactivating")] + Deactivating, + #[strum(serialize = "failed", to_string = "Failed")] + Failed, +} + /* /// Process #[derive(Clone, Debug)] @@ -641,8 +671,8 @@ pub struct Unit { pub utype: Type, /// Optional unit description pub description: Option, - /// Current state - pub state: State, + /// Current loaded state + pub loaded_state: LoadedState, /// Auto start feature pub auto_start: AutoStartStatus, /// `true` if Self is actively running @@ -864,7 +894,7 @@ mod test { .get_or_insert_with(Vec::new) .push(Doc::Man("some instruction".into())); u.auto_start = AutoStartStatus::Transient; - u.state = State::Loaded; + u.loaded_state = LoadedState::Loaded; u.utype = Type::Socket; // serde let json_u = serde_json::to_string(&u).unwrap(); From b2934553f322309b9d106f3462573de87030e546 Mon Sep 17 00:00:00 2001 From: Joseph Fox-Rabinovitz Date: Thu, 1 May 2025 11:14:17 -0500 Subject: [PATCH 2/6] add list_dependencies() --- README.md | 3 +++ src/lib.rs | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/README.md b/README.md index b75e0e7..094369c 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,9 @@ systemctl.list_units(Some("service"), None, Some("cron*")); // Check if a unit is active systemctl.get_active_state("service"); + +// list dependencies of a service or target +systemctl.list_dependencies("some.target"); ``` ## Unit structure diff --git a/src/lib.rs b/src/lib.rs index 7f2c62c..6cb740b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -170,6 +170,18 @@ impl SystemCtl { ) } + /// Returns a list of services that are dependencies of the given unit + pub fn list_dependencies(&self, unit: &str) -> std::io::Result> { + let output = self.systemctl_capture(vec!["list-dependencies", unit])?; + let mut dependencies = Vec::::new(); + for line in output.lines().skip(1) { + dependencies.push(String::from( + line.replace(|c: char| !c.is_ascii(), "").trim(), + )); + } + Ok(dependencies) +} + /// Isolates given unit, only self and its dependencies are /// now actively running pub fn isolate(&self, unit: &str) -> std::io::Result { @@ -851,6 +863,7 @@ mod test { } } } + #[test] fn test_list_units_full() { let units = ctl().list_unit_files_full(None, None, None).unwrap(); // all units @@ -885,6 +898,14 @@ mod test { } } + #[test] + fn test_list_dependencies() { + let units = ctl().list_dependencies("sound.target").unwrap(); + for unit in units { + println!("{unit}"); + } + } + #[cfg(feature = "serde")] #[test] fn test_serde_for_unit() { From 847abb935e54651b58485e8cd3efd4f3b731f8a3 Mon Sep 17 00:00:00 2001 From: Joseph Fox-Rabinovitz Date: Thu, 1 May 2025 11:34:55 -0500 Subject: [PATCH 3/6] update states in list_units_full() --- src/lib.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6cb740b..c42cfd7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -296,8 +296,8 @@ impl SystemCtl { result.push(UnitService { unit_name: parsed[0].to_string(), - loaded: parsed[1].to_string(), - state: parsed[2].to_string(), + loaded: LoadedState::from_str(parsed[1]).unwrap_or(LoadedState::Unknown), + active: ActiveState::from_str(parsed[2]).unwrap_or(ActiveState::Unknown), sub_state: parsed[3].to_string(), description, }) @@ -511,9 +511,9 @@ pub struct UnitService { /// Unit name: `name.type` pub unit_name: String, /// Loaded state - pub loaded: String, + pub loaded: LoadedState, /// Unit state - pub state: String, + pub active: ActiveState, /// Unit substate pub sub_state: String, /// Unit description @@ -571,12 +571,15 @@ pub enum Type { /// `LoadedState` describes a Unit's current loaded state #[derive(Copy, Clone, PartialEq, Eq, EnumString, Debug, Default)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(not(feature = "serde"), derive(strum_macros::Display))] pub enum LoadedState { - #[strum(serialize = "masked")] + #[strum(serialize = "masked", to_string = "Masked")] #[default] Masked, - #[strum(serialize = "loaded")] + #[strum(serialize = "loaded", to_string = "Loaded")] Loaded, + #[strum(serialize = "", to_string = "Unknown")] + Unknown, } /// `ActiveState` describes a Unit's current active state @@ -595,6 +598,10 @@ pub enum ActiveState { Deactivating, #[strum(serialize = "failed", to_string = "Failed")] Failed, + #[strum(serialize = "reloading", to_string = "Reloading")] + Reloading, + #[strum(serialize = "", to_string = "Unknown")] + Unknown, } /* From 903084e4aa4f754226865c32acd6630d5680cbb4 Mon Sep 17 00:00:00 2001 From: Joseph Fox-Rabinovitz Date: Thu, 1 May 2025 12:48:44 -0500 Subject: [PATCH 4/6] cargo fmt --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index c42cfd7..16d0d50 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -180,7 +180,7 @@ impl SystemCtl { )); } Ok(dependencies) -} + } /// Isolates given unit, only self and its dependencies are /// now actively running From 4fa081a4f34daf30e576012eae69d3f76f089b7a Mon Sep 17 00:00:00 2001 From: Joseph Fox-Rabinovitz Date: Thu, 1 May 2025 15:02:29 -0500 Subject: [PATCH 5/6] add raw_ versions for some methods --- src/lib.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 16d0d50..3721f09 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -173,8 +173,12 @@ impl SystemCtl { /// Returns a list of services that are dependencies of the given unit pub fn list_dependencies(&self, unit: &str) -> std::io::Result> { let output = self.systemctl_capture(vec!["list-dependencies", unit])?; + Self::list_dependencies_from_raw(output) + } + + pub fn list_dependencies_from_raw(raw: String) -> std::io::Result> { let mut dependencies = Vec::::new(); - for line in output.lines().skip(1) { + for line in raw.lines().skip(1) { dependencies.push(String::from( line.replace(|c: char| !c.is_ascii(), "").trim(), )); @@ -275,31 +279,28 @@ impl SystemCtl { if let Some(glob) = glob { args.push(glob) } + let content = self.systemctl_capture(args)?; + Self::list_units_full_from_raw(content) + } + + pub fn list_units_full_from_raw(raw: String) -> std::io::Result> { let mut result: Vec = Vec::new(); - let content = self.systemctl_capture(args.clone())?; - let lines = content + let lines = raw .lines() .filter(|line| line.contains('.') && !line.ends_with('.')); for l in lines { // fixes format for not found units let slice = if l.starts_with("● ") { &l[3..] } else { l }; - let parsed: Vec<&str> = slice.split_ascii_whitespace().collect(); - let description = parsed - .split_at(4) - .1 - .iter() - .fold("".to_owned(), |acc, str| format!("{} {}", acc, str)); - result.push(UnitService { unit_name: parsed[0].to_string(), loaded: LoadedState::from_str(parsed[1]).unwrap_or(LoadedState::Unknown), active: ActiveState::from_str(parsed[2]).unwrap_or(ActiveState::Unknown), sub_state: parsed[3].to_string(), - description, + description: parsed[4..].join(" "), }) } Ok(result) From 0218e4ca552e4cf06ef1c14f9f65a49bca400707 Mon Sep 17 00:00:00 2001 From: Joseph Fox-Rabinovitz Date: Fri, 2 May 2025 12:11:41 -0500 Subject: [PATCH 6/6] cargo clippy --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3721f09..817f0c9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -161,12 +161,12 @@ impl SystemCtl { /// Returns active state of the given `unit` pub fn get_active_state(&self, unit: &str) -> std::io::Result { let status = self.systemctl_capture(vec!["is-active", unit])?; - ActiveState::from_str(&status.trim_end()).map_or( + ActiveState::from_str(status.trim_end()).map_or( Err(Error::new( ErrorKind::InvalidData, format!("Invalid status {}", status), )), - |x| Ok(x), + Ok, ) }