summaryrefslogtreecommitdiffstats
path: root/crates/core/tedge
diff options
context:
space:
mode:
authorRina Fujino <18257209+rina23q@users.noreply.github.com>2022-01-10 17:57:41 +0100
committerGitHub <noreply@github.com>2022-01-10 17:57:41 +0100
commit3ab4cf779ebacafd5990285d07ccfe03bebaea03 (patch)
treeae8f06bc774a39f2575abf933cb92a04fc710a79 /crates/core/tedge
parent9c4dee566f25a7a3a2b2548a03d0c4a4bf65b66e (diff)
#639 Remove the dependency on systemd (#729)
* The purpose of this change is to get rid of the hard-coded dependencies on systemd from tedge connect/disconnect, to allow users to use other system managers, e.g. OpenRC, initd, etc.. * If /etc/tedge/system.toml exists, tedge connect/disconnect uses the service manager defined in the file. * If the file is not given by user, tedge connect/disconnect uses /bin/systemctl as the service manager. (the same behaviour as we have it so far) * Delete old service implementation files for BSD, OpenRC, systemd, and NULL. * Add system.toml example files for BSD and OpenRC. * Add a reference guide to explain the format of system.toml configuration file. Signed-off-by: Rina Fujino <18257209+rina23q@users.noreply.github.com>
Diffstat (limited to 'crates/core/tedge')
-rw-r--r--crates/core/tedge/src/cli/connect/cli.rs15
-rw-r--r--crates/core/tedge/src/cli/connect/command.rs4
-rw-r--r--crates/core/tedge/src/cli/disconnect/cli.rs15
-rw-r--r--crates/core/tedge/src/cli/disconnect/disconnect_bridge.rs2
-rw-r--r--crates/core/tedge/src/command.rs3
-rw-r--r--crates/core/tedge/src/error.rs5
-rw-r--r--crates/core/tedge/src/main.rs15
-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
17 files changed, 617 insertions, 739 deletions
diff --git a/crates/core/tedge/src/cli/connect/cli.rs b/crates/core/tedge/src/cli/connect/cli.rs
index 42e68885..1f3e2643 100644
--- a/crates/core/tedge/src/cli/connect/cli.rs
+++ b/crates/core/tedge/src/cli/connect/cli.rs
@@ -1,5 +1,6 @@
use crate::cli::connect::*;
use crate::command::{BuildCommand, BuildContext, Command};
+use crate::system_services::service_manager;
use structopt::StructOpt;
#[derive(StructOpt, Debug, PartialEq)]
@@ -27,20 +28,26 @@ impl BuildCommand for TEdgeConnectOpt {
fn build_command(self, context: BuildContext) -> Result<Box<dyn Command>, crate::ConfigError> {
Ok(match self {
TEdgeConnectOpt::C8y { is_test_connection } => ConnectCommand {
- config_location: context.config_location,
+ config_location: context.config_location.clone(),
config_repository: context.config_repository,
cloud: Cloud::C8y,
common_mosquitto_config: CommonMosquittoConfig::default(),
is_test_connection,
- service_manager: context.service_manager.clone(),
+ service_manager: service_manager(
+ context.user_manager.clone(),
+ context.config_location.tedge_config_root_path,
+ )?,
},
TEdgeConnectOpt::Az { is_test_connection } => ConnectCommand {
- config_location: context.config_location,
+ config_location: context.config_location.clone(),
config_repository: context.config_repository,
cloud: Cloud::Azure,
common_mosquitto_config: CommonMosquittoConfig::default(),
is_test_connection,
- service_manager: context.service_manager.clone(),
+ service_manager: service_manager(
+ context.user_manager.clone(),
+ context.config_location.tedge_config_root_path,
+ )?,
},
}
.into_boxed())
diff --git a/crates/core/tedge/src/cli/connect/command.rs b/crates/core/tedge/src/cli/connect/command.rs
index fb2f9d17..262e6163 100644
--- a/crates/core/tedge/src/cli/connect/command.rs
+++ b/crates/core/tedge/src/cli/connect/command.rs
@@ -431,7 +431,9 @@ fn new_bridge(
println!("Checking if {} is available.\n", service_manager.name());
let service_manager_result = service_manager.check_operational();
- if let Err(SystemServiceError::ServiceManagerUnavailable(name)) = &service_manager_result {
+ if let Err(SystemServiceError::ServiceManagerUnavailable { cmd: _, name }) =
+ &service_manager_result
+ {
println!(
"Warning: '{}' service manager is not available on the system.\n",
name
diff --git a/crates/core/tedge/src/cli/disconnect/cli.rs b/crates/core/tedge/src/cli/disconnect/cli.rs
index 12181669..1b77aff6 100644
--- a/crates/core/tedge/src/cli/disconnect/cli.rs
+++ b/crates/core/tedge/src/cli/disconnect/cli.rs
@@ -1,5 +1,6 @@
use crate::cli::disconnect::disconnect_bridge::*;
use crate::command::*;
+use crate::system_services::service_manager;
use structopt::StructOpt;
const C8Y_CONFIG_FILENAME: &str = "c8y-bridge.conf";
@@ -17,20 +18,26 @@ impl BuildCommand for TEdgeDisconnectBridgeCli {
fn build_command(self, context: BuildContext) -> Result<Box<dyn Command>, crate::ConfigError> {
let cmd = match self {
TEdgeDisconnectBridgeCli::C8y => DisconnectBridgeCommand {
- config_location: context.config_location,
+ config_location: context.config_location.clone(),
config_file: C8Y_CONFIG_FILENAME.into(),
cloud: Cloud::C8y,
use_mapper: true,
use_agent: true,
- service_manager: context.service_manager.clone(),
+ service_manager: service_manager(
+ context.user_manager.clone(),
+ context.config_location.tedge_config_root_path,
+ )?,
},
TEdgeDisconnectBridgeCli::Az => DisconnectBridgeCommand {
- config_location: context.config_location,
+ config_location: context.config_location.clone(),
config_file: AZURE_CONFIG_FILENAME.into(),
cloud: Cloud::Azure,
use_mapper: true,
use_agent: false,
- service_manager: context.service_manager.clone(),
+ service_manager: service_manager(
+ context.user_manager.clone(),
+ context.config_location.tedge_config_root_path,
+ )?,
},
};
Ok(cmd.into_boxed())
diff --git a/crates/core/tedge/src/cli/disconnect/disconnect_bridge.rs b/crates/core/tedge/src/cli/disconnect/disconnect_bridge.rs
index d1854a8c..295dd641 100644
--- a/crates/core/tedge/src/cli/disconnect/disconnect_bridge.rs
+++ b/crates/core/tedge/src/cli/disconnect/disconnect_bridge.rs
@@ -63,7 +63,7 @@ impl DisconnectBridgeCommand {
// If this fails, do not continue with applying changes and stopping/disabling tedge-mapper.
self.remove_bridge_config_file()?;
- if let Err(SystemServiceError::ServiceManagerUnavailable(name)) =
+ if let Err(SystemServiceError::ServiceManagerUnavailable { cmd: _, name }) =
self.service_manager.check_operational()
{
println!(
diff --git a/crates/core/tedge/src/command.rs b/crates/core/tedge/src/command.rs
index 530d519b..6637d0da 100644
--- a/crates/core/tedge/src/command.rs
+++ b/crates/core/tedge/src/command.rs
@@ -1,5 +1,3 @@
-use crate::system_services::*;
-use std::sync::Arc;
use tedge_users::UserManager;
/// A trait to be implemented by all tedge sub-commands.
@@ -152,6 +150,5 @@ pub trait BuildCommand {
pub struct BuildContext {
pub config_repository: tedge_config::TEdgeConfigRepository,
pub config_location: tedge_config::TEdgeConfigLocation,
- pub service_manager: Arc<dyn SystemServiceManager>,
pub user_manager: UserManager,
}
diff --git a/crates/core/tedge/src/error.rs b/crates/core/tedge/src/error.rs
index 3e4bb431..46aa68d3 100644
--- a/crates/core/tedge/src/error.rs
+++ b/crates/core/tedge/src/error.rs
@@ -1,3 +1,5 @@
+use crate::system_services;
+
#[derive(thiserror::Error, Debug)]
pub enum TEdgeError {
#[error("TOML parse error")]
@@ -20,4 +22,7 @@ pub enum TEdgeError {
#[error(transparent)]
FromRumqttClient(#[from] rumqttc::ClientError),
+
+ #[error(transparent)]
+ FromSystemServiceError(#[from] system_services::SystemServiceError),
}
diff --git a/crates/core/tedge/src/main.rs b/crates/core/tedge/src/main.rs
index 8c2df446..e6313ab3 100644
--- a/crates/core/tedge/src/main.rs
+++ b/crates/core/tedge/src/main.rs
@@ -1,9 +1,7 @@
#![forbid(unsafe_code)]
#![deny(clippy::mem_forget)]
-use crate::system_services::*;
use anyhow::Context;
-use std::sync::Arc;
use structopt::StructOpt;
use tedge_users::UserManager;
use tedge_utils::paths::{home_dir, PathsError};
@@ -36,7 +34,6 @@ fn main() -> anyhow::Result<()> {
let build_context = BuildContext {
config_repository,
config_location: tedge_config_location,
- service_manager: service_manager(user_manager.clone()),
user_manager,
};
@@ -48,15 +45,3 @@ fn main() -> anyhow::Result<()> {
cmd.execute()
.with_context(|| format!("failed to {}", cmd.description()))
}
-
-fn service_manager(user_manager: UserManager) -> Arc<dyn SystemServiceManager> {
- if cfg!(feature = "openrc") {
- Arc::new(OpenRcServiceManager::new(user_manager))
- } else if cfg!(target_os = "linux") {
- Arc::new(SystemdServiceManager::new(user_manager))
- } else if cfg!(target_os = "freebsd") {
- Arc::new(BsdServiceManager::new(user_manager))
- } else {
- Arc::new(NullSystemServiceManager)
- }
-}
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,
+ })
+ }
+}
+