#![doc = include_str!("../README.md")]
#[doc = include_str!("../README.md")]
#[cfg(doctest)]
pub struct ReadmeDoctests;
use std::{
ffi::{OsStr, OsString},
fmt, io,
path::PathBuf,
str::FromStr,
};
mod kind;
mod launchd;
mod openrc;
mod rcd;
mod sc;
mod systemd;
mod typed;
mod utils;
mod winsw;
pub use kind::*;
pub use launchd::*;
pub use openrc::*;
pub use rcd::*;
pub use sc::*;
pub use systemd::*;
pub use typed::*;
pub use winsw::*;
pub trait ServiceManager {
fn available(&self) -> io::Result<bool>;
fn install(&self, ctx: ServiceInstallCtx) -> io::Result<()>;
fn uninstall(&self, ctx: ServiceUninstallCtx) -> io::Result<()>;
fn start(&self, ctx: ServiceStartCtx) -> io::Result<()>;
fn stop(&self, ctx: ServiceStopCtx) -> io::Result<()>;
fn level(&self) -> ServiceLevel;
fn set_level(&mut self, level: ServiceLevel) -> io::Result<()>;
fn status(&self, ctx: ServiceStatusCtx) -> io::Result<ServiceStatus>;
}
impl dyn ServiceManager {
pub fn target_or_native(
kind: impl Into<Option<ServiceManagerKind>>,
) -> io::Result<Box<dyn ServiceManager>> {
Ok(TypedServiceManager::target_or_native(kind)?.into_box())
}
pub fn target(kind: ServiceManagerKind) -> Box<dyn ServiceManager> {
TypedServiceManager::target(kind).into_box()
}
pub fn native() -> io::Result<Box<dyn ServiceManager>> {
native_service_manager()
}
}
#[inline]
pub fn native_service_manager() -> io::Result<Box<dyn ServiceManager>> {
Ok(TypedServiceManager::native()?.into_box())
}
impl<'a, S> From<S> for Box<dyn ServiceManager + 'a>
where
S: ServiceManager + 'a,
{
fn from(service_manager: S) -> Self {
Box::new(service_manager)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum ServiceLevel {
System,
User,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum RestartPolicy {
Never,
Always {
delay_secs: Option<u32>,
},
OnFailure {
delay_secs: Option<u32>,
max_retries: Option<u32>,
reset_after_secs: Option<u32>,
},
OnSuccess {
delay_secs: Option<u32>,
},
}
impl Default for RestartPolicy {
fn default() -> Self {
RestartPolicy::OnFailure {
delay_secs: None,
max_retries: None,
reset_after_secs: None,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum ServiceStatus {
NotInstalled,
Running,
Stopped(Option<String>), }
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ServiceLabel {
pub qualifier: Option<String>,
pub organization: Option<String>,
pub application: String,
}
impl ServiceLabel {
pub fn to_qualified_name(&self) -> String {
let mut qualified_name = String::new();
if let Some(qualifier) = self.qualifier.as_ref() {
qualified_name.push_str(qualifier.as_str());
qualified_name.push('.');
}
if let Some(organization) = self.organization.as_ref() {
qualified_name.push_str(organization.as_str());
qualified_name.push('.');
}
qualified_name.push_str(self.application.as_str());
qualified_name
}
pub fn to_script_name(&self) -> String {
let mut script_name = String::new();
if let Some(organization) = self.organization.as_ref() {
script_name.push_str(organization.as_str());
script_name.push('-');
}
script_name.push_str(self.application.as_str());
script_name
}
}
impl fmt::Display for ServiceLabel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.to_qualified_name().as_str())
}
}
impl FromStr for ServiceLabel {
type Err = io::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let tokens = s.split('.').collect::<Vec<&str>>();
let label = match tokens.len() {
1 => Self {
qualifier: None,
organization: None,
application: tokens[0].to_string(),
},
2 => Self {
qualifier: None,
organization: Some(tokens[0].to_string()),
application: tokens[1].to_string(),
},
3 => Self {
qualifier: Some(tokens[0].to_string()),
organization: Some(tokens[1].to_string()),
application: tokens[2].to_string(),
},
_ => Self {
qualifier: Some(tokens[0].to_string()),
organization: Some(tokens[1].to_string()),
application: tokens[2..].join("."),
},
};
Ok(label)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ServiceInstallCtx {
pub label: ServiceLabel,
pub program: PathBuf,
pub args: Vec<OsString>,
pub contents: Option<String>,
pub username: Option<String>,
pub working_directory: Option<PathBuf>,
pub environment: Option<Vec<(String, String)>>,
pub autostart: bool,
pub restart_policy: RestartPolicy,
}
impl ServiceInstallCtx {
pub fn cmd_iter(&self) -> impl Iterator<Item = &OsStr> {
std::iter::once(self.program.as_os_str()).chain(self.args_iter())
}
pub fn args_iter(&self) -> impl Iterator<Item = &OsStr> {
self.args.iter().map(OsString::as_os_str)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ServiceUninstallCtx {
pub label: ServiceLabel,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ServiceStartCtx {
pub label: ServiceLabel,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ServiceStopCtx {
pub label: ServiceLabel,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ServiceStatusCtx {
pub label: ServiceLabel,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_service_label_parssing_1() {
let label = ServiceLabel::from_str("com.example.app123").unwrap();
assert_eq!(label.qualifier, Some("com".to_string()));
assert_eq!(label.organization, Some("example".to_string()));
assert_eq!(label.application, "app123".to_string());
assert_eq!(label.to_qualified_name(), "com.example.app123");
assert_eq!(label.to_script_name(), "example-app123");
}
#[test]
fn test_service_label_parssing_2() {
let label = ServiceLabel::from_str("example.app123").unwrap();
assert_eq!(label.qualifier, None);
assert_eq!(label.organization, Some("example".to_string()));
assert_eq!(label.application, "app123".to_string());
assert_eq!(label.to_qualified_name(), "example.app123");
assert_eq!(label.to_script_name(), "example-app123");
}
#[test]
fn test_service_label_parssing_3() {
let label = ServiceLabel::from_str("app123").unwrap();
assert_eq!(label.qualifier, None);
assert_eq!(label.organization, None);
assert_eq!(label.application, "app123".to_string());
assert_eq!(label.to_qualified_name(), "app123");
assert_eq!(label.to_script_name(), "app123");
}
}