|
|
|
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));
|
|
|
|
}
|
|
|
|
}
|