You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

467 lines
14 KiB
Rust

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<Unit>,
}
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<String>,
// Space -and- list
pub(crate) wants: Vec<String>,
pub(crate) requires: Vec<String>,
pub(crate) requisite: Vec<String>,
pub(crate) binds_to: Vec<String>,
pub(crate) part_of: Vec<String>,
// Space only
pub(crate) conflicts: Vec<String>,
// Space -and- list
pub(crate) before: Vec<String>,
pub(crate) after: Vec<String>,
// Space only
pub(crate) on_failure: Vec<String>,
// Space -and- list
pub(crate) propagates_reload_to: Vec<String>,
pub(crate) propagates_reload_from: Vec<String>,
pub(crate) joins_namespace_of: Vec<String>,
pub(crate) requires_mounts_for: Vec<String>,
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<u8>,
pub(crate) success_action_exit_status: Option<u8>,
pub(crate) job_timeout: TimeSpan,
pub(crate) job_running_timeout: TimeSpan,
pub(crate) job_timeout_action: UnitAction,
pub(crate) job_timeout_reboot_argument: Option<String>,
pub(crate) start_limit_interval: FiniteTimeSpan,
pub(crate) start_limit_burst: usize,
pub(crate) start_limit_action: UnitAction,
pub(crate) reboot_argument: Option<String>,
pub(crate) source_path: Option<String>,
}
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<Unit> {
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::<u8>().ok())
.or(unit.failure_action_exit_status);
unit.success_action_exit_status = section
.get_string("SuccessActionExitStatus")
.and_then(|x| x.parse::<u8>().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::<usize>().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<String>;
}
impl StringFromStr for Vec<&str> {
fn to_string(&self) -> Vec<String> {
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<JobMode> {
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<CollectMode> {
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<UnitAction> {
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));
}
}