use std::collections::{BTreeMap, BTreeSet};
use super::command::run_command;
pub(super) fn fetch_active_units() -> Result<BTreeSet<String>, String> {
let output = run_command(
"systemctl",
&[
"list-units",
"--type=service",
"--no-legend",
"--state=active",
],
"systemctl list-units --type=service",
)?;
Ok(parse_active_units(&output))
}
pub(super) fn parse_active_units(systemctl_output: &str) -> BTreeSet<String> {
systemctl_output
.lines()
.filter_map(|line| {
let unit = line.split_whitespace().next()?;
if unit.ends_with(".service") {
Some(unit.to_string())
} else {
None
}
})
.collect()
}
pub(super) fn fetch_active_service_binaries(
active_units: &BTreeSet<String>,
) -> BTreeMap<String, Vec<String>> {
let mut unit_to_binaries: BTreeMap<String, Vec<String>> = BTreeMap::new();
for unit in active_units {
match run_command(
"systemctl",
&["show", unit, "-p", "ExecStart"],
&format!("systemctl show {unit} -p ExecStart"),
) {
Ok(output) => {
let binaries = parse_execstart_paths(&output);
if !binaries.is_empty() {
unit_to_binaries.insert(unit.clone(), binaries);
}
}
Err(err) => {
tracing::debug!("Failed to get ExecStart for {}: {}", unit, err);
}
}
}
unit_to_binaries
}
pub(super) fn parse_execstart_paths(systemctl_output: &str) -> Vec<String> {
let mut binaries = Vec::new();
for line in systemctl_output.lines() {
if let Some(exec_line) = line.strip_prefix("ExecStart=") {
let path = exec_line
.split_whitespace()
.next()
.unwrap_or(exec_line)
.trim_matches('"')
.trim_matches('\'');
if !path.is_empty() && !path.starts_with('-') {
binaries.push(path.to_string());
}
} else if line.starts_with("ExecStartPre=") || line.starts_with("ExecStartPost=") {
if let Some(exec_line) = line.split_once('=') {
let path = exec_line
.1
.split_whitespace()
.next()
.unwrap_or(exec_line.1)
.trim_matches('"')
.trim_matches('\'');
if !path.is_empty() && !path.starts_with('-') {
binaries.push(path.to_string());
}
}
}
}
binaries
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_active_units_extracts_first_column() {
let output = "\
sshd.service loaded active running OpenSSH Daemon
cups.service loaded active running CUPS Scheduler
dbus.socket loaded active running D-Bus Socket
";
let active = parse_active_units(output);
let expected: BTreeSet<String> = ["sshd.service", "cups.service"]
.iter()
.map(ToString::to_string)
.collect();
assert_eq!(active, expected);
}
}