diff --git a/sysf-init/src/main.rs b/sysf-init/src/main.rs index d560e21..c75f9b2 100644 --- a/sysf-init/src/main.rs +++ b/sysf-init/src/main.rs @@ -1,6 +1,9 @@ extern crate sysf; +use sysf::unit::Unit; -fn main() { +fn main() { + let x = Unit::new("hello"); + println!("{:?}", x); } diff --git a/sysf/Cargo.toml b/sysf/Cargo.toml index d1ea460..c3f7d7b 100644 --- a/sysf/Cargo.toml +++ b/sysf/Cargo.toml @@ -2,5 +2,4 @@ name = "sysf" version = "0.1.0" authors = ["eater <=@eater.me>"] -edition = "2018" -crate-type = "dylib" \ No newline at end of file +edition = "2018" \ No newline at end of file diff --git a/sysf/src/config/mod.rs b/sysf/src/config/mod.rs index 4916c4e..f9d45cb 100644 --- a/sysf/src/config/mod.rs +++ b/sysf/src/config/mod.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use crate::config::parser::{ParserError, parse_config}; use crate::time::{parse_finite_time_span, FiniteTimeSpan, TimeSpan, parse_time_span}; +use crate::unit::Unit; pub mod parser; @@ -21,8 +22,16 @@ impl Config { id } - pub fn load_file(&mut self, file_name: String, file_contents: String) -> Result<(), ParserError> { - parse_config(self, file_name, file_contents) + pub fn section(&self, name: &str) -> Option<&Section> { + self.sections.get(name) + } + + pub fn load_file(&mut self, file_name: &str, file_contents: &str) -> Result<(), ParserError> { + parse_config(self, file_name.to_string(), file_contents.to_string()) + } + + pub fn get_unit(&self) -> Option { + Unit::load_from_config(&self.name, self) } pub fn new(name: &str) -> Config { @@ -73,6 +82,26 @@ impl Section { pub fn get_time_span(&self, name: &str) -> Option { self.entries.get(name).and_then(|x| x.get_time_span()) } + + pub fn get_space_separated_list(&self, name: &str) -> Vec<&str> { + self + .get_list(name) + .unwrap_or(vec![]) + .iter() + .flat_map(|x| x.split(" ")) + .filter(|x| x.len() > 0) + .collect() + } + + pub fn get_space_separated_string(&self, name: &str) -> Vec<&str> { + self + .get_string(name) + .unwrap_or("") + .trim() + .split(" ") + .filter(|x| x.len() > 0) + .collect() + } } #[derive(Clone, Debug, Default)] diff --git a/sysf/src/time.rs b/sysf/src/time.rs index def5d7b..98a5260 100644 --- a/sysf/src/time.rs +++ b/sysf/src/time.rs @@ -3,7 +3,7 @@ use crate::time::TimeSpan::{Infinite, Finite}; #[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)] pub enum TimeSpan { Infinite, - Finite(FiniteTimeSpan) + Finite(FiniteTimeSpan), } #[derive(Default, Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)] diff --git a/sysf/src/unit.rs b/sysf/src/unit.rs index 4fd4f8f..0d06e7e 100644 --- a/sysf/src/unit.rs +++ b/sysf/src/unit.rs @@ -1,9 +1,15 @@ use crate::unit::JobMode::Replace; use crate::time::{TimeSpan, FiniteTimeSpan}; use crate::time::TimeSpan::Infinite; +use crate::unit::UnitState::Stopped; +use crate::config::Config; +use crate::unit::JobMode::{Fail, ReplaceIrreversibly, Isolate, Flush, IgnoreDependencies, IgnoreRequirements}; +use crate::unit::CollectMode::{Inactive, InactiveOrFailed}; -#[allow(dead_code)] +#[derive(Ord, PartialOrd, Eq, PartialEq, Debug, Clone)] pub struct Unit { + name: String, + state: UnitState, description: String, // Space only @@ -43,8 +49,8 @@ pub struct Unit { collect_mode: CollectMode, failure_action: UnitAction, success_action: UnitAction, - failure_action_exit_status: u8, - success_action_exit_status: u8, + failure_action_exit_status: Option, + success_action_exit_status: Option, job_timeout_sec: TimeSpan, job_running_timeout_sec: TimeSpan, job_timeout_action: UnitAction, @@ -56,9 +62,192 @@ pub struct Unit { 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("Requirers") + .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_sec = section + .get_time_span("JobTimeoutSec") + .unwrap_or(unit.job_timeout_sec); + + unit.job_running_timeout_sec = section + .get_time_span("JobRunningTimeoutSec") + .unwrap_or(unit.job_running_timeout_sec); + + 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_sec = section + .get_finite_time_span("StartLimitIntervalSec") + .unwrap_or(unit.start_limit_interval_sec); + + 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![], @@ -84,8 +273,8 @@ impl Default for Unit { collect_mode: CollectMode::Inactive, failure_action: UnitAction::None, success_action: UnitAction::None, - failure_action_exit_status: 0, - success_action_exit_status: 0, + failure_action_exit_status: None, + success_action_exit_status: None, job_timeout_sec: Infinite, job_running_timeout_sec: Infinite, job_timeout_action: UnitAction::None, @@ -99,7 +288,15 @@ impl Default for Unit { } } -enum JobMode { +#[derive(Ord, PartialOrd, Eq, PartialEq, Debug, Copy, Clone)] +pub enum UnitState { + Stopped, + Failed, + Running, +} + +#[derive(Ord, PartialOrd, Eq, PartialEq, Debug, Copy, Clone)] +pub enum JobMode { Fail, Replace, ReplaceIrreversibly, @@ -109,11 +306,38 @@ enum JobMode { IgnoreRequirements, } -enum CollectMode { +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)] enum UnitAction { None, Reboot(UnitActionSeverity), @@ -121,8 +345,67 @@ enum UnitAction { Exit(UnitActionSeverity), } -enum 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, +} + +#[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)); + + } } \ No newline at end of file