summaryrefslogtreecommitdiffstats
path: root/crates/core/tedge/src/system_services
diff options
context:
space:
mode:
Diffstat (limited to 'crates/core/tedge/src/system_services')
-rw-r--r--crates/core/tedge/src/system_services/command_builder.rs8
-rw-r--r--crates/core/tedge/src/system_services/error.rs79
-rw-r--r--crates/core/tedge/src/system_services/manager.rs13
-rw-r--r--crates/core/tedge/src/system_services/managers/bsd.rs199
-rw-r--r--crates/core/tedge/src/system_services/managers/config.rs138
-rw-r--r--crates/core/tedge/src/system_services/managers/general_manager.rs401
-rw-r--r--crates/core/tedge/src/system_services/managers/mod.rs8
-rw-r--r--crates/core/tedge/src/system_services/managers/null.rs35
-rw-r--r--crates/core/tedge/src/system_services/managers/openrc.rs199
-rw-r--r--crates/core/tedge/src/system_services/managers/systemd.rs217
10 files changed, 586 insertions, 711 deletions
diff --git a/crates/core/tedge/src/system_services/command_builder.rs b/crates/core/tedge/src/system_services/command_builder.rs
index 9d9e8ffb..bb66f313 100644
--- a/crates/core/tedge/src/system_services/command_builder.rs
+++ b/crates/core/tedge/src/system_services/command_builder.rs
@@ -13,8 +13,12 @@ impl CommandBuilder {
}
}
- pub fn arg(mut self, arg: impl AsRef<OsStr>) -> CommandBuilder {
- self.command.arg(arg);
+ pub fn args<I, S>(mut self, args: I) -> CommandBuilder
+ where
+ I: IntoIterator<Item = S>,
+ S: AsRef<OsStr>,
+ {
+ self.command.args(args);
self
}
diff --git a/crates/core/tedge/src/system_services/error.rs b/crates/core/tedge/src/system_services/error.rs
index 1b2e8c1a..0de341b3 100644
--- a/crates/core/tedge/src/system_services/error.rs
+++ b/crates/core/tedge/src/system_services/error.rs
@@ -1,63 +1,34 @@
#[derive(thiserror::Error, Debug)]
pub enum SystemServiceError {
- #[error(transparent)]
- IoError(#[from] std::io::Error),
-
- #[error(transparent)]
- SystemdError(#[from] SystemdError),
-
- #[error(transparent)]
- OpenRcServiceError(#[from] OpenRcServiceError),
-
- #[error(transparent)]
- BsdServiceError(#[from] BsdServiceError),
-
- #[error("Unexpected value for exit status.")]
- UnexpectedExitStatus,
-
- #[error("Unsupported operation.")]
- UnsupportedOperation,
-
- #[error("Service Manager: '{0}' is not available on the system or elevated permissions have not been granted.")]
- ServiceManagerUnavailable(String),
-}
-
-/// The error type used by the `SystemdServiceManager`
-#[derive(thiserror::Error, Debug)]
-pub enum SystemdError {
- #[error("Systemd returned unspecific error for service {service} while performing {cmd} it.\nHint: {hint}")]
- UnspecificError {
- service: &'static str,
- cmd: &'static str,
- hint: &'static str,
- },
-
- #[error("Service {service} not found. Install {service} to use this command.")]
- ServiceNotFound { service: &'static str },
+ #[error("Service command <{service_command:?}> failed with code: {code:?}.")]
+ ServiceCommandFailedWithCode { service_command: String, code: i32 },
- #[error("Service {service} not loaded.")]
- ServiceNotLoaded { service: &'static str },
+ #[error("Service command <{service_command:?}> terminated by a signal.")]
+ ServiceCommandFailedBySignal { service_command: String },
- #[error("Returned exit code: '{code:?}' for: systemd' is unhandled.")]
- UnhandledReturnCode { code: i32 },
-}
-
-/// The error type used by the `OpenRcServiceManager`
-#[derive(thiserror::Error, Debug)]
-pub enum OpenRcServiceError {
- #[error("Service command <{service_command:?}> failed with code: {code:?}.")]
- ServiceCommandFailed {
+ #[error(
+ "Service command <{service_command:?}> not found.\n\
+ Check '{path}' file."
+ )]
+ ServiceCommandNotFound {
service_command: String,
- code: Option<i32>,
+ path: String,
},
-}
-/// The error type used by the `BsdServiceManager`
-#[derive(thiserror::Error, Debug)]
-pub enum BsdServiceError {
- #[error("Service command <{service_command:?}> failed with code: {code:?}.")]
- ServiceCommandFailed {
- service_command: String,
- code: Option<i32>,
+ #[error("Failed to execute '{cmd}' to check the service manager availability.\n\
+ Service manager '{name}' is not available on the system or elevated permissions have not been granted.")]
+ ServiceManagerUnavailable { cmd: String, name: String },
+
+ #[error("Toml syntax error in the system config file '{path}': {reason}")]
+ SystemConfigInvalidToml { path: String, reason: String },
+
+ #[error(
+ "Syntax error in the system config file for '{cmd}': {reason}\n\
+ Check '{path}' file."
+ )]
+ SystemConfigInvalidSyntax {
+ reason: String,
+ cmd: String,
+ path: String,
},
}
diff --git a/crates/core/tedge/src/system_services/manager.rs b/crates/core/tedge/src/system_services/manager.rs
index 899f6386..7c39ea8c 100644
--- a/crates/core/tedge/src/system_services/manager.rs
+++ b/crates/core/tedge/src/system_services/manager.rs
@@ -1,5 +1,8 @@
use crate::system_services::*;
use std::fmt::Debug;
+use std::path::PathBuf;
+use std::sync::Arc;
+use tedge_users::UserManager;
/// Abstraction over the system-provided facility that manages starting, stopping as well as other
/// service-related management functions of system services.
@@ -38,3 +41,13 @@ pub trait SystemServiceManager: Debug {
}
}
}
+
+pub fn service_manager(
+ user_manager: UserManager,
+ config_root: PathBuf,
+) -> Result<Arc<dyn SystemServiceManager>, SystemServiceError> {
+ Ok(Arc::new(GeneralServiceManager::try_new(
+ user_manager,
+ config_root,
+ )?))
+}
diff --git a/crates/core/tedge/src/system_services/managers/bsd.rs b/crates/core/tedge/src/system_services/managers/bsd.rs
deleted file mode 100644
index bca56a1c..00000000
--- a/crates/core/tedge/src/system_services/managers/bsd.rs
+++ /dev/null
@@ -1,199 +0,0 @@
-use crate::system_services::*;
-use std::process::ExitStatus;
-use tedge_users::{UserManager, ROOT_USER};
-
-/// Service manager that uses `service(8)` as found on FreeBSD to control system services.
-///
-#[derive(Debug)]
-pub struct BsdServiceManager {
- user_manager: UserManager,
-}
-
-impl BsdServiceManager {
- pub fn new(user_manager: UserManager) -> Self {
- Self { user_manager }
- }
-}
-
-impl SystemServiceManager for BsdServiceManager {
- fn name(&self) -> &str {
- "service(8)"
- }
-
- fn check_operational(&self) -> Result<(), SystemServiceError> {
- let mut command = ServiceCommand::CheckManager.into_command();
-
- match command.status() {
- Ok(status) if status.success() => Ok(()),
- _ => Err(SystemServiceError::ServiceManagerUnavailable(
- self.name().to_string(),
- )),
- }
- }
-
- fn stop_service(&self, service: SystemService) -> Result<(), SystemServiceError> {
- let service_command = ServiceCommand::Stop(service);
-
- self.run_service_command_as_root(service_command)?
- .must_succeed()
- }
-
- fn restart_service(&self, service: SystemService) -> Result<(), SystemServiceError> {
- let service_command = ServiceCommand::Restart(service);
-
- self.run_service_command_as_root(service_command)?
- .must_succeed()
- }
-
- fn enable_service(&self, service: SystemService) -> Result<(), SystemServiceError> {
- let service_command = ServiceCommand::Enable(service);
-
- self.run_service_command_as_root(service_command)?
- .must_succeed()
- }
-
- fn disable_service(&self, service: SystemService) -> Result<(), SystemServiceError> {
- let service_command = ServiceCommand::Disable(service);
-
- self.run_service_command_as_root(service_command)?
- .must_succeed()
- }
-
- fn is_service_running(&self, service: SystemService) -> Result<bool, SystemServiceError> {
- let service_command = ServiceCommand::IsActive(service);
-
- self.run_service_command_as_root(service_command)
- .map(|status| status.success())
- }
-}
-
-impl BsdServiceManager {
- fn run_service_command_as_root(
- &self,
- service_command: ServiceCommand,
- ) -> Result<ServiceCommandExitStatus, SystemServiceError> {
- let _root_guard = self.user_manager.become_user(ROOT_USER);
-
- service_command
- .into_command()
- .status()
- .map_err(Into::into)
- .map(|status| ServiceCommandExitStatus {
- status,
- service_command,
- })
- }
-}
-
-struct ServiceCommandExitStatus {
- status: ExitStatus,
- service_command: ServiceCommand,
-}
-
-impl ServiceCommandExitStatus {
- fn must_succeed(self) -> Result<(), SystemServiceError> {
- if self.status.success() {
- Ok(())
- } else {
- Err(BsdServiceError::ServiceCommandFailed {
- service_command: self.service_command.to_string(),
- code: self.status.code(),
- }
- .into())
- }
- }
-
- fn success(self) -> bool {
- self.status.success()
- }
-}
-
-const SERVICE_BIN: &str = "/usr/sbin/service";
-
-#[derive(Debug, Copy, Clone)]
-enum ServiceCommand {
- CheckManager,
- Stop(SystemService),
- Restart(SystemService),
- Enable(SystemService),
- Disable(SystemService),
- IsActive(SystemService),
-}
-
-impl ServiceCommand {
- fn to_string(self) -> String {
- match self {
- Self::CheckManager => format!("{} -l", SERVICE_BIN),
- Self::Stop(service) => format!(
- "{} {} stop",
- SERVICE_BIN,
- SystemService::as_service_name(service)
- ),
- Self::Restart(service) => {
- format!(
- "{} {} restart",
- SERVICE_BIN,
- SystemService::as_service_name(service)
- )
- }
- Self::Enable(service) => {
- format!(
- "{} {} enable",
- SERVICE_BIN,
- SystemService::as_service_name(service)
- )
- }
- Self::Disable(service) => {
- format!(
- "{} {} forcedisable",
- SERVICE_BIN,
- SystemService::as_service_name(service)
- )
- }
- Self::IsActive(service) => {
- format!(
- "{} {} status",
- SERVICE_BIN,
- SystemService::as_service_name(service)
- )
- }
- }
- }
-
- fn into_command(self) -> std::process::Command {
- match self {
- Self::CheckManager => CommandBuilder::new(SERVICE_BIN).arg("-l").silent().build(),
- Self::Stop(service) => CommandBuilder::new(SERVICE_BIN)
- .arg(SystemService::as_service_name(service))
- .arg("stop")
- .silent()
- .build(),
- Self::Restart(service) => CommandBuilder::new(SERVICE_BIN)
- .arg(SystemService::as_service_name(service))
- .arg("restart")
- .silent()
- .build(),
- Self::Enable(service) => CommandBuilder::new(SERVICE_BIN)
- .arg(SystemService::as_service_name(service))
- .arg("enable")
- .silent()
- .build(),
-
- Self::Disable(service) => CommandBuilder::new(SERVICE_BIN)
- .arg(SystemService::as_service_name(service))
- //
- // Use "forcedisable" as otherwise it could fail if you have a commented out
- // `# mosquitto_enable="YES"` or
- // `# mosquitto_enable="NO"` in your `/etc/rc.conf` file.
- //
- .arg("forcedisable")
- .silent()
- .build(),
- Self::IsActive(service) => CommandBuilder::new(SERVICE_BIN)
- .arg(SystemService::as_service_name(service))
- .arg("status")
- .silent()
- .build(),
- }
- }
-}
diff --git a/crates/core/tedge/src/system_services/managers/config.rs b/crates/core/tedge/src/system_services/managers/config.rs
new file mode 100644
index 00000000..af495f02
--- /dev/null
+++ b/crates/core/tedge/src/system_services/managers/config.rs
@@ -0,0 +1,138 @@
+use crate::system_services::*;
+use serde::Deserialize;
+use std::fs;
+use std::path::PathBuf;
+
+pub const SERVICE_CONFIG_FILE: &str = "system.toml";
+
+#[derive(Deserialize, Debug, PartialEq)]
+pub struct SystemConfig {
+ pub(crate) init: InitConfig,
+}
+
+#[derive(Deserialize, Debug, PartialEq)]
+#[serde(deny_unknown_fields)]
+pub struct InitConfig {
+ pub name: String,
+ pub is_available: Vec<String>,
+ pub restart: Vec<String>,
+ pub stop: Vec<String>,
+ pub enable: Vec<String>,
+ pub disable: Vec<String>,
+ pub is_active: Vec<String>,
+}
+
+impl Default for InitConfig {
+ fn default() -> Self {
+ Self {
+ name: "systemd".to_string(),
+ is_available: vec!["/bin/systemctl".into(), "--version".into()],
+ restart: vec!["/bin/systemctl".into(), "restart".into(), "{}".into()],
+ stop: vec!["/bin/systemctl".into(), "stop".into(), "{}".into()],
+ enable: vec!["/bin/systemctl".into(), "enable".into(), "{}".into()],
+ disable: vec!["/bin/systemctl".into(), "disable".into(), "{}".into()],
+ is_active: vec!["/bin/systemctl".into(), "is-active".into(), "{}".into()],
+ }
+ }
+}
+
+impl Default for SystemConfig {
+ fn default() -> Self {
+ Self {
+ init: InitConfig::default(),
+ }
+ }
+}
+
+impl SystemConfig {
+ pub fn try_new(config_root: PathBuf) -> Result<Self, SystemServiceError> {
+ let config_path = config_root.join(SERVICE_CONFIG_FILE);
+ let config_path_str = config_path.to_str().unwrap_or(SERVICE_CONFIG_FILE);
+
+ match fs::read_to_string(config_path.clone()) {
+ Ok(contents) => {
+ let config: SystemConfig = toml::from_str(contents.as_str()).map_err(|e| {
+ SystemServiceError::SystemConfigInvalidToml {
+ path: config_path_str.to_string(),
+ reason: format!("{}", e),
+ }
+ })?;
+ Ok(config)
+ }
+ Err(_) => {
+ println!("The system config file '{}' doesn't exist. Use '/bin/systemctl' as a service manager.\n", config_path_str);
+ Ok(Self::default())
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::io::Write;
+ use tempfile::TempDir;
+
+ #[test]
+ fn deserialize_system_config() {
+ let config: SystemConfig = toml::from_str(
+ r#"
+ [init]
+ name = "systemd"
+ is_available = ["/bin/systemctl", "--version"]
+ restart = ["/bin/systemctl", "restart", "{}"]
+ stop = ["/bin/systemctl", "stop", "{}"]
+ enable = ["/bin/systemctl", "enable", "{}"]
+ disable = ["/bin/systemctl", "disable", "{}"]
+ is_active = ["/bin/systemctl", "is-active", "{}"]
+ "#,
+ )
+ .unwrap();
+
+ assert_eq!(config.init.name, "systemd");
+ assert_eq!(
+ config.init.is_available,
+ vec!["/bin/systemctl", "--version"]
+ );
+ assert_eq!(config.init.restart, vec!["/bin/systemctl", "restart", "{}"]);
+ assert_eq!(config.init.stop, vec!["/bin/systemctl", "stop", "{}"]);
+ assert_eq!(config.init.enable, vec!["/bin/systemctl", "enable", "{}"]);
+ assert_eq!(config.init.disable, vec!["/bin/systemctl", "disable", "{}"]);
+ assert_eq!(
+ config.init.is_active,
+ vec!["/bin/systemctl", "is-active", "{}"]
+ );
+ }
+
+ #[test]
+ fn read_system_config_file() -> anyhow::Result<()> {
+ let toml_conf = r#"
+ [init]
+ name = "systemd"
+ is_available = ["/bin/systemctl", "--version"]
+ restart = ["/bin/systemctl", "restart", "{}"]
+ stop = ["/bin/systemctl", "stop", "{}"]
+ enable = ["/bin/systemctl", "enable", "{}"]
+ disable = ["/bin/systemctl", "disable", "{}"]
+ is_active = ["/bin/systemctl", "is-active", "{}"]
+ "#;
+ let expected_config: SystemConfig = toml::from_str(toml_conf)?;
+
+ let (_dir, config_root_path) = create_temp_system_config(toml_conf)?;
+ let config = SystemConfig::try_new(config_root_path).unwrap();
+
+ assert_eq!(config, expected_config);
+
+ Ok(())
+ }
+
+ // Need to return TempDir, otherwise the dir will be deleted when this function ends.
+ fn create_temp_system_config(content: &str) -> std::io::Result<(TempDir, PathBuf)> {
+ let temp_dir = TempDir::new()?;
+ let config_root = temp_dir.path().to_path_buf();
+ let config_file_path = config_root.join(SERVICE_CONFIG_FILE);
+ let mut file = std::fs::File::create(config_file_path.as_path())?;
+ file.write_all(content.as_bytes())?;
+ Ok((temp_dir, config_root))
+ }
+}
diff --git a/crates/core/tedge/src/system_services/managers/general_manager.rs b/crates/core/tedge/src/system_services/managers/general_manager.rs
new file mode 100644
index 00000000..fad556e3
--- /dev/null
+++ b/crates/core/tedge/src/system_services/managers/general_manager.rs
@@ -0,0 +1,401 @@
+use crate::system_services::{
+ CommandBuilder, InitConfig, SystemConfig, SystemService, SystemServiceError,
+ SystemServiceManager, SERVICE_CONFIG_FILE,
+};
+use std::fmt;
+use std::path::PathBuf;
+use std::process::ExitStatus;
+use tedge_users::{UserManager, ROOT_USER};
+
+#[derive(Debug)]
+pub struct GeneralServiceManager {
+ user_manager: UserManager,
+ init_config: InitConfig,
+ config_path: String,
+}
+
+impl GeneralServiceManager {
+ pub fn try_new(
+ user_manager: UserManager,
+ config_root: PathBuf,
+ ) -> Result<Self, SystemServiceError> {
+ let init_config = SystemConfig::try_new(config_root.clone())?.init;
+ let config_path = config_root
+ .join(SERVICE_CONFIG_FILE)
+ .to_str()
+ .unwrap_or(SERVICE_CONFIG_FILE)
+ .to_string();
+ Ok(Self {
+ user_manager,
+ init_config,
+ config_path,
+ })
+ }
+}
+
+impl SystemServiceManager for GeneralServiceManager {
+ fn name(&self) -> &str {
+ &self.init_config.name
+ }
+
+ fn check_operational(&self) -> Result<(), SystemServiceError> {
+ let exec_command = ServiceCommand::CheckManager.try_exec_command(self)?;
+
+ match exec_command.to_command().status() {
+ Ok(status) if status.success() => Ok(()),
+ _ => Err(SystemServiceError::ServiceManagerUnavailable {
+ cmd: exec_command.to_string(),
+ name: self.name().to_string(),
+ }),
+ }
+ }
+
+ fn stop_service(&self, service: SystemService) -> Result<(), SystemServiceError> {
+ let exec_command = ServiceCommand::Stop(service).try_exec_command(self)?;
+ self.run_service_command_as_root(exec_command, self.config_path.as_str())?
+ .must_succeed()
+ }
+
+ fn restart_service(&self, service: SystemService) -> Result<(), SystemServiceError> {
+ let exec_command = ServiceCommand::Restart(service).try_exec_command(self)?;
+ self.run_service_command_as_root(exec_command, self.config_path.as_str())?
+ .must_succeed()
+ }
+
+ fn enable_service(&self, service: SystemService) -> Result<(), SystemServiceError> {
+ let exec_command = ServiceCommand::Enable(service).try_exec_command(self)?;
+ self.run_service_command_as_root(exec_command, self.config_path.as_str())?
+ .must_succeed()
+ }
+
+ fn disable_service(&self, service: SystemService) -> Result<(), SystemServiceError> {
+ let exec_command = ServiceCommand::Disable(service).try_exec_command(self)?;
+ self.run_service_command_as_root(exec_command, self.config_path.as_str())?
+ .must_succeed()
+ }
+
+ fn is_service_running(&self, service: SystemService) -> Result<bool, SystemServiceError> {
+ let exec_command = ServiceCommand::IsActive(service).try_exec_command(self)?;
+ self.run_service_command_as_root(exec_command, self.config_path.as_str())
+ .map(|status| status.success())
+ }
+}
+
+#[derive(Debug, PartialEq)]
+struct ExecCommand {
+ exec: String,
+ args: Vec<String>,
+}
+
+impl ExecCommand {
+ fn try_new(
+ config: Vec<String>,
+ cmd: ServiceCommand,
+ config_path: String,
+ ) -> Result<Self, SystemServiceError> {
+ match config.split_first() {
+ Some((exec, args)) => Ok(Self {
+ exec: exec.to_string(),
+ args: args.to_vec(),
+ }),
+ None => Err(SystemServiceError::SystemConfigInvalidSyntax {
+ reason: "Requires 1 or more arguments.".to_string(),
+ cmd: cmd.to_string(),
+ path: config_path,
+ }),
+ }
+ }
+
+ fn try_new_with_placeholder(
+ config: Vec<String>,
+ service_cmd: ServiceCommand,
+ config_path: String,
+ service: SystemService,
+ ) -> Result<Self, SystemServiceError> {
+ let replaced =
+ replace_with_service_name(&config, service_cmd, config_path.as_str(), service)?;
+ Self::try_new(replaced, service_cmd, config_path)
+ }
+
+ fn to_command(&self) -> std::process::Command {
+ CommandBuilder::new(&self.exec)
+ .args(&self.args)
+ .silent()
+ .build()
+ }
+}
+
+impl fmt::Display for ExecCommand {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ if self.args.is_empty() {
+ write!(f, "{}", self.exec)
+ } else {
+ let mut s = self.exec.to_owned();
+ for arg in &self.args {
+ s = format!("{} {}", s, arg);
+ }
+ write!(f, "{}", s)
+ }
+ }
+}
+
+fn replace_with_service_name(
+ input_args: &[String],
+ service_cmd: ServiceCommand,
+ config_path: &str,
+ service: SystemService,
+) -> Result<Vec<String>, SystemServiceError> {
+ if !input_args.iter().any(|s| s == "{}") {
+ return Err(SystemServiceError::SystemConfigInvalidSyntax {
+ reason: "A placeholder '{}' is missing.".to_string(),
+ cmd: service_cmd.to_string(),
+ path: config_path.to_string(),
+ });
+ }
+
+ let mut args = input_args.to_owned();
+ for item in args.iter_mut() {
+ if item == "{}" {
+ *item = SystemService::as_service_name(service).to_string();
+ }
+ }
+
+ Ok(args)
+}
+
+#[derive(Debug, Copy, Clone)]
+enum ServiceCommand {
+ CheckManager,
+ Stop(SystemService),
+ Restart(SystemService),
+ Enable(SystemService),
+ Disable(SystemService),
+ IsActive(SystemService),
+}
+
+impl ServiceCommand {
+ fn try_exec_command(
+ &self,
+ service_manager: &GeneralServiceManager,
+ ) -> Result<ExecCommand, SystemServiceError> {
+ let config_path = service_manager.config_path.clone();
+ match self {
+ Self::CheckManager => ExecCommand::try_new(
+ service_manager.init_config.is_available.clone(),
+ ServiceCommand::CheckManager,
+ config_path,
+ ),
+ Self::Stop(service) => ExecCommand::try_new_with_placeholder(
+ service_manager.init_config.stop.clone(),
+ ServiceCommand::Stop(*service),
+ config_path,
+ *service,
+ ),
+ Self::Restart(service) => ExecCommand::try_new_with_placeholder(
+ service_manager.init_config.restart.clone(),
+ ServiceCommand::Restart(*service),
+ config_path,
+ *service,
+ ),
+ Self::Enable(service) => ExecCommand::try_new_with_placeholder(
+ service_manager.init_config.enable.clone(),
+ ServiceCommand::Enable(*service),
+ config_path,
+ *service,
+ ),
+ Self::Disable(service) => ExecCommand::try_new_with_placeholder(
+ service_manager.init_config.disable.clone(),
+ ServiceCommand::Disable(*service),
+ config_path,
+ *service,
+ ),
+ Self::IsActive(service) => ExecCommand::try_new_with_placeholder(
+ service_manager.init_config.is_active.clone(),
+ ServiceCommand::IsActive(*service),
+ config_path,
+ *service,
+ ),
+ }
+ }
+}
+
+impl fmt::Display for ServiceCommand {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Self::CheckManager => write!(f, "is_available"),
+ Self::Stop(_service) => write!(f, "stop"),
+ Self::Restart(_service) => write!(f, "restart"),
+ Self::Enable(_service) => write!(f, "enable"),
+ Self::Disable(_service) => write!(f, "disable"),
+ Self::IsActive(_service) => write!(f, "is_active"),
+ }
+ }
+}
+
+impl GeneralServiceManager {
+ fn run_service_command_as_root(
+ &self,
+ exec_command: ExecCommand,
+ config_path: &str,
+ ) -> Result<ServiceCommandExitStatus, SystemServiceError> {
+ let _root_guard = self.user_manager.become_user(ROOT_USER);
+
+ exec_command
+ .to_command()
+ .status()
+ .map_err(|_| SystemServiceError::ServiceCommandNotFound {
+ service_command: exec_command.to_string(),
+ path: config_path.to_string(),
+ })
+ .map(|status| ServiceCommandExitStatus {
+ status,
+ service_command: exec_command.to_string(),
+ })
+ }
+}
+
+#[derive(Debug)]
+struct ServiceCommandExitStatus {
+ status: ExitStatus,
+ service_command: String,
+}
+
+impl ServiceCommandExitStatus {
+ fn must_succeed(self) -> Result<(), SystemServiceError> {
+ if self.status.success() {
+ Ok(())
+ } else {
+ match self.status.code() {
+ Some(code) => Err(SystemServiceError::ServiceCommandFailedWithCode {
+ service_command: self.service_command,
+ code,
+ }),
+ None => Err(SystemServiceError::ServiceCommandFailedBySignal {
+ service_command: self.service_command,
+ }),
+ }
+ }
+ }
+
+ fn success(self) -> bool {
+ self.status.success()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use assert_matches::*;
+ use test_case::test_case;
+
+ #[test_case(
+ vec!["bin".to_string(), "{}".to_string(), "arg2".to_string()],
+ vec!["bin".to_string(), "mosquitto".to_string(), "arg2".to_string()]
+ ;"one placeholder")]
+ #[test_case(
+ vec!["bin".to_string(), "{}".to_string(), "{}".to_string()],
+ vec!["bin".to_string(), "mosquitto".to_string(), "mosquitto".to_string()]
+ ;"several placeholders")]
+ fn replace_placeholder_with_service(input: Vec<String>, expected_output: Vec<String>) {
+ let replaced_config = replace_with_service_name(
+ &input,
+ ServiceCommand::Stop(SystemService::Mosquitto),
+ "/dummy/path.toml",
+ SystemService::Mosquitto,
+ )
+ .unwrap();
+ assert_eq!(replaced_config, expected_output)
+ }
+
+ #[test]
+ fn fail_to_replace_placeholder_with_service() {
+ let input = vec!["bin".to_string(), "arg1".to_string(), "arg2".to_string()];
+ let system_config_error = replace_with_service_name(
+ &input,
+ ServiceCommand::Stop(SystemService::Mosquitto),
+ "dummy/path.toml",
+ SystemService::Mosquitto,
+ )
+ .unwrap_err();
+ assert_matches!(
+ system_config_error,
+ SystemServiceError::SystemConfigInvalidSyntax { .. }
+ )
+ }
+
+ #[test_case(
+ vec!["bin".to_string(), "arg1".to_string(), "arg2".to_string()],
+ ExecCommand {
+ exec: "bin".to_string(),
+ args: vec!["arg1".to_string(), "arg2".to_string()]
+ }
+ ;"with arguments")]
+ #[test_case(
+ vec!["bin".to_string()],
+ ExecCommand {
+ exec: "bin".to_string(),
+ args: vec![]
+ }
+ ;"only executable")]
+ fn build_exec_command(config: Vec<String>, expected: ExecCommand) {
+ let exec_command = ExecCommand::try_new(
+ config,
+ ServiceCommand::Stop(SystemService::Mosquitto),
+ "test/dummy.toml".to_string(),
+ )
+ .unwrap();
+ assert_eq!(exec_command, expected);
+ }
+
+ #[test]
+ fn fail_to_build_exec_command() {
+ let config = vec![];
+ let system_config_error = ExecCommand::try_new(
+ config,
+ ServiceCommand::Stop(SystemService::Mosquitto),
+ "test/dummy.toml".to_string(),
+ )
+ .unwrap_err();
+ assert_matches!(
+ system_config_error,
+ SystemServiceError::SystemConfigInvalidSyntax { .. }
+ );
+ }
+
+ #[test_case(
+ ExecCommand {
+ exec: "bin".to_string(),
+ args: vec!["arg1".to_string(), "arg2".to_string()]
+ },
+ r#""bin" "arg1" "arg2""#
+ ;"with a