use crate::unit::JobMode::Replace; use crate::time::{TimeSpan, FiniteTimeSpan}; use crate::time::TimeSpan::Infinite; use crate::unit::UnitState::{Stopped, Unloaded}; use crate::config::Config; use crate::unit::JobMode::{Fail, ReplaceIrreversibly, Isolate, Flush, IgnoreDependencies, IgnoreRequirements}; use crate::unit::CollectMode::{Inactive, InactiveOrFailed}; #[derive(Ord, PartialOrd, Eq, PartialEq, Debug, Clone)] pub struct UnitHandle { name: String, config: Option, } impl UnitHandle { pub fn from_unit(unit: Unit) -> UnitHandle { UnitHandle { name: unit.name.clone(), config: Some(unit), } } pub fn unloaded(name: &str) -> UnitHandle { UnitHandle { name: name.to_string(), config: None, } } pub fn is_loaded(&self) -> bool { self.config.is_some() } pub fn get_state(&self) -> UnitState { if let Some(unit) = &self.config { unit.state } else { Unloaded } } pub fn update(&mut self, unit: Unit) { self.config = Some(unit); } pub fn get_config(&self) -> Option<&Unit> { if let Some(ref unit) = self.config { Some(unit) } else { None } } } #[derive(Ord, PartialOrd, Eq, PartialEq, Debug, Clone)] pub struct Unit { pub(crate) name: String, pub(crate) state: UnitState, pub(crate) description: String, pub(crate) documentation: Vec, // Space -and- list pub(crate) wants: Vec, pub(crate) requires: Vec, pub(crate) requisite: Vec, pub(crate) binds_to: Vec, pub(crate) part_of: Vec, // Space only pub(crate) conflicts: Vec, // Space -and- list pub(crate) before: Vec, pub(crate) after: Vec, // Space only pub(crate) on_failure: Vec, // Space -and- list pub(crate) propagates_reload_to: Vec, pub(crate) propagates_reload_from: Vec, pub(crate) joins_namespace_of: Vec, pub(crate) requires_mounts_for: Vec, pub(crate) on_failure_job_mode: JobMode, pub(crate) ignore_on_isolate: bool, pub(crate) stop_when_unneeded: bool, pub(crate) refuse_manual_start: bool, pub(crate) refuse_manual_stop: bool, pub(crate) allow_isolate: bool, pub(crate) default_dependencies: bool, pub(crate) collect_mode: CollectMode, pub(crate) failure_action: UnitAction, pub(crate) success_action: UnitAction, pub(crate) failure_action_exit_status: Option, pub(crate) success_action_exit_status: Option, pub(crate) job_timeout: TimeSpan, pub(crate) job_running_timeout: TimeSpan, pub(crate) job_timeout_action: UnitAction, pub(crate) job_timeout_reboot_argument: Option, pub(crate) start_limit_interval: FiniteTimeSpan, pub(crate) start_limit_burst: usize, pub(crate) start_limit_action: UnitAction, pub(crate) reboot_argument: Option, pub(crate) source_path: Option, } impl Unit { pub fn new(name: &str) -> Unit { let mut unit = Unit::default(); unit.name = name.to_string(); unit } // RsBorrowCheck says unit is moved, even though it isn't //noinspection RsBorrowChecker pub(crate) fn load_from_config(name: &str, config: &Config) -> Option { let section = config.section("Unit")?; let mut unit = Unit::new(name); unit.description = section .get_string("Description") .map(str::to_string) .unwrap_or(unit.description); unit.documentation = section .get_space_separated_string("Documentation") .to_string(); unit.wants = section .get_space_separated_list("Wants") .to_string(); unit.requires = section .get_space_separated_list("Requires") .to_string(); unit.requisite = section .get_space_separated_list("Requisite") .to_string(); unit.binds_to = section .get_space_separated_list("BindsTo") .to_string(); unit.part_of = section .get_space_separated_list("PartOf") .to_string(); unit.conflicts = section .get_space_separated_string("Conflicts") .to_string(); unit.before = section .get_space_separated_list("Before") .to_string(); unit.after = section .get_space_separated_list("After") .to_string(); unit.on_failure = section .get_space_separated_string("OnFailure") .to_string(); unit.propagates_reload_to = section .get_space_separated_list("PropagatesReloadTo") .to_string(); unit.propagates_reload_from = section .get_space_separated_list("PropagatesReloadFrom") .to_string(); unit.joins_namespace_of = section .get_space_separated_list("JoinsNamespaceOf") .to_string(); unit.requires_mounts_for = section .get_space_separated_list("RequiresMountsFor") .to_string(); unit.on_failure_job_mode = section .get_string("OnFailureJobMode") .and_then(JobMode::from_str) .unwrap_or(unit.on_failure_job_mode); unit.ignore_on_isolate = section .get_boolean("IgnoreOnIsolate") .unwrap_or(unit.ignore_on_isolate); unit.stop_when_unneeded = section .get_boolean("StopWhenUnneeded") .unwrap_or(unit.stop_when_unneeded); unit.refuse_manual_start = section .get_boolean("RefuseManualStart") .unwrap_or(unit.refuse_manual_start); unit.refuse_manual_stop = section .get_boolean("RefuseManualStop") .unwrap_or(unit.refuse_manual_stop); unit.allow_isolate = section .get_boolean("AllowIsolate") .unwrap_or(unit.allow_isolate); unit.default_dependencies = section .get_boolean("DefaultDependencies") .unwrap_or(unit.default_dependencies); unit.collect_mode = section .get_string("CollectMode") .and_then(CollectMode::from_str) .unwrap_or(unit.collect_mode); unit.failure_action = section .get_string("FailureAction") .and_then(UnitAction::from_str) .unwrap_or(unit.failure_action); unit.success_action = section .get_string("SuccessAction") .and_then(UnitAction::from_str) .unwrap_or(unit.success_action); unit.failure_action_exit_status = section .get_string("FailureActionExitStatus") .and_then(|x| x.parse::().ok()) .or(unit.failure_action_exit_status); unit.success_action_exit_status = section .get_string("SuccessActionExitStatus") .and_then(|x| x.parse::().ok()) .or(unit.success_action_exit_status); unit.job_timeout = section .get_time_span("JobTimeoutSec") .unwrap_or(unit.job_timeout); unit.job_running_timeout = section .get_time_span("JobRunningTimeoutSec") .unwrap_or(unit.job_running_timeout); unit.job_timeout_action = section .get_string("JobTimeoutAction") .and_then(UnitAction::from_str) .unwrap_or(unit.job_timeout_action); unit.job_timeout_reboot_argument = section .get_string("JobTimeoutRebootArgument") .map(str::to_string); unit.start_limit_interval = section .get_finite_time_span("StartLimitIntervalSec") .unwrap_or(unit.start_limit_interval); unit.start_limit_burst = section .get_string("StartLimitBurst") .and_then(|x| x.parse::().ok()) .unwrap_or(unit.start_limit_burst); unit.start_limit_action = section .get_string("StartLimitAction") .and_then(UnitAction::from_str) .unwrap_or(unit.start_limit_action); unit.reboot_argument = section .get_string("RebootArgument") .map(str::to_string); unit.source_path = section .get_string("SourcePath") .map(str::to_string); Some(unit) } } trait StringFromStr { fn to_string(&self) -> Vec; } impl StringFromStr for Vec<&str> { fn to_string(&self) -> Vec { self.iter().map(|x| x.to_string()).collect() } } impl Default for Unit { fn default() -> Self { Unit { name: "".to_string(), state: Stopped, description: "".to_string(), documentation: vec![], wants: vec![], requires: vec![], requisite: vec![], binds_to: vec![], part_of: vec![], conflicts: vec![], before: vec![], after: vec![], on_failure: vec![], propagates_reload_to: vec![], propagates_reload_from: vec![], joins_namespace_of: vec![], requires_mounts_for: vec![], on_failure_job_mode: Replace, ignore_on_isolate: false, stop_when_unneeded: false, refuse_manual_start: false, refuse_manual_stop: false, allow_isolate: false, default_dependencies: true, collect_mode: CollectMode::Inactive, failure_action: UnitAction::None, success_action: UnitAction::None, failure_action_exit_status: None, success_action_exit_status: None, job_timeout: Infinite, job_running_timeout: Infinite, job_timeout_action: UnitAction::None, job_timeout_reboot_argument: None, start_limit_interval: Default::default(), start_limit_burst: 0, start_limit_action: UnitAction::None, reboot_argument: None, source_path: None, } } } #[derive(Ord, PartialOrd, Eq, PartialEq, Debug, Copy, Clone)] pub enum UnitState { Stopped, Failed, Running, Unloaded, } #[derive(Ord, PartialOrd, Eq, PartialEq, Debug, Copy, Clone)] pub enum JobMode { Fail, Replace, ReplaceIrreversibly, Isolate, Flush, IgnoreDependencies, IgnoreRequirements, } impl JobMode { fn from_str(value: &str) -> Option { Some(match value.trim().to_ascii_lowercase().as_str() { "fail" => Fail, "replace" => Replace, "replace-irreversibly" => ReplaceIrreversibly, "isolate" => Isolate, "flush" => Flush, "ignore-dependencies" => IgnoreDependencies, "ignore-requirements" => IgnoreRequirements, _ => return None }) } } #[derive(Ord, PartialOrd, Eq, PartialEq, Debug, Copy, Clone)] pub enum CollectMode { Inactive, InactiveOrFailed, } impl CollectMode { pub fn from_str(value: &str) -> Option { Some(match value.trim().to_ascii_lowercase().as_str() { "inactive" => Inactive, "inactive-or-failed" => InactiveOrFailed, _ => return None }) } } #[derive(Ord, PartialOrd, Eq, PartialEq, Debug, Copy, Clone)] pub enum UnitAction { None, Reboot(UnitActionSeverity), PowerOff(UnitActionSeverity), Exit(UnitActionSeverity), } impl UnitAction { pub fn from_str(value: &str) -> Option { Some(match value.trim().to_ascii_lowercase().as_str() { "none" => UnitAction::None, "reboot" => UnitAction::Reboot(UnitActionSeverity::None), "reboot-force" => UnitAction::Reboot(UnitActionSeverity::Force), "reboot-immediate" => UnitAction::Reboot(UnitActionSeverity::Immediate), "poweroff" => UnitAction::PowerOff(UnitActionSeverity::None), "poweroff-force" => UnitAction::PowerOff(UnitActionSeverity::Force), "poweroff-immediate" => UnitAction::PowerOff(UnitActionSeverity::Immediate), "exit" => UnitAction::Exit(UnitActionSeverity::None), "exit-force" => UnitAction::Exit(UnitActionSeverity::Force), _ => return None }) } } #[derive(Ord, PartialOrd, Eq, PartialEq, Debug, Copy, Clone)] pub enum UnitActionSeverity { None, Force, Immediate, } pub enum UnitType { Service, Socket, Device, Mount, AutoMount, Swap, Target, Path, Slice, Scope, } #[cfg(test)] mod tests { use super::*; #[test] fn test_simple_unit_parsing() { let mut config = Config::new("test.unit"); let res = config.load_file("/test.unit", r" [Unit] Description=It's just a test. Documentation=none :) Documentation=man:test https://example.com Before=sysinit.target multi-user.target Before=ye.target Before= Before=ohno.target After=time.target After=myself.target you.target FailureAction=reboot-immediate CollectMode=inactive-or-failed FailureActionExitStatus=1 SuccessActionExitStatus=256 "); assert!(res.is_ok()); let unit = config.get_unit(); assert!(unit.is_some()); let unit = unit.unwrap(); assert_eq!(unit.description, "It's just a test."); assert_eq!(unit.documentation, vec!["man:test", "https://example.com"]); assert_eq!(unit.before, vec!["ohno.target"]); assert_eq!(unit.after, vec!["time.target", "myself.target", "you.target"]); assert_eq!(unit.failure_action, UnitAction::Reboot(UnitActionSeverity::Immediate)); assert_eq!(unit.collect_mode, CollectMode::InactiveOrFailed); assert_eq!(unit.success_action_exit_status, None); assert_eq!(unit.failure_action_exit_status, Some(1)); } }