1#![doc = include_str!("../README.md")]
2
3#[doc = include_str!("../README.md")]
4#[cfg(doctest)]
5pub struct ReadmeDoctests;
6
7use std::{
8 ffi::{OsStr, OsString},
9 fmt, io,
10 path::PathBuf,
11 str::FromStr,
12};
13
14mod kind;
15mod launchd;
16mod openrc;
17mod rcd;
18mod sc;
19mod systemd;
20mod typed;
21mod utils;
22mod winsw;
23
24pub use kind::*;
25pub use launchd::*;
26pub use openrc::*;
27pub use rcd::*;
28pub use sc::*;
29pub use systemd::*;
30pub use typed::*;
31pub use winsw::*;
32
33pub trait ServiceManager {
35 fn available(&self) -> io::Result<bool>;
38
39 fn install(&self, ctx: ServiceInstallCtx) -> io::Result<()>;
41
42 fn uninstall(&self, ctx: ServiceUninstallCtx) -> io::Result<()>;
44
45 fn start(&self, ctx: ServiceStartCtx) -> io::Result<()>;
47
48 fn stop(&self, ctx: ServiceStopCtx) -> io::Result<()>;
50
51 fn level(&self) -> ServiceLevel;
53
54 fn set_level(&mut self, level: ServiceLevel) -> io::Result<()>;
56
57 fn status(&self, ctx: ServiceStatusCtx) -> io::Result<ServiceStatus>;
59}
60
61impl dyn ServiceManager {
62 pub fn target_or_native(
65 kind: impl Into<Option<ServiceManagerKind>>,
66 ) -> io::Result<Box<dyn ServiceManager>> {
67 Ok(TypedServiceManager::target_or_native(kind)?.into_box())
68 }
69
70 pub fn target(kind: ServiceManagerKind) -> Box<dyn ServiceManager> {
73 TypedServiceManager::target(kind).into_box()
74 }
75
76 pub fn native() -> io::Result<Box<dyn ServiceManager>> {
83 native_service_manager()
84 }
85}
86
87#[inline]
94pub fn native_service_manager() -> io::Result<Box<dyn ServiceManager>> {
95 Ok(TypedServiceManager::native()?.into_box())
96}
97
98impl<'a, S> From<S> for Box<dyn ServiceManager + 'a>
99where
100 S: ServiceManager + 'a,
101{
102 fn from(service_manager: S) -> Self {
103 Box::new(service_manager)
104 }
105}
106
107#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
109pub enum ServiceLevel {
110 System,
111 User,
112}
113
114#[derive(Clone, Debug, PartialEq, Eq, Hash)]
116pub enum ServiceStatus {
117 NotInstalled,
118 Running,
119 Stopped(Option<String>), }
121
122#[derive(Clone, Debug, PartialEq, Eq, Hash)]
124pub struct ServiceLabel {
125 pub qualifier: Option<String>,
129
130 pub organization: Option<String>,
134
135 pub application: String,
139}
140
141impl ServiceLabel {
142 pub fn to_qualified_name(&self) -> String {
144 let mut qualified_name = String::new();
145 if let Some(qualifier) = self.qualifier.as_ref() {
146 qualified_name.push_str(qualifier.as_str());
147 qualified_name.push('.');
148 }
149 if let Some(organization) = self.organization.as_ref() {
150 qualified_name.push_str(organization.as_str());
151 qualified_name.push('.');
152 }
153 qualified_name.push_str(self.application.as_str());
154 qualified_name
155 }
156
157 pub fn to_script_name(&self) -> String {
160 let mut script_name = String::new();
161 if let Some(organization) = self.organization.as_ref() {
162 script_name.push_str(organization.as_str());
163 script_name.push('-');
164 }
165 script_name.push_str(self.application.as_str());
166 script_name
167 }
168}
169
170impl fmt::Display for ServiceLabel {
171 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173 f.write_str(self.to_qualified_name().as_str())
174 }
175}
176
177impl FromStr for ServiceLabel {
178 type Err = io::Error;
179
180 fn from_str(s: &str) -> Result<Self, Self::Err> {
182 let tokens = s.split('.').collect::<Vec<&str>>();
183
184 let label = match tokens.len() {
185 1 => Self {
186 qualifier: None,
187 organization: None,
188 application: tokens[0].to_string(),
189 },
190 2 => Self {
191 qualifier: None,
192 organization: Some(tokens[0].to_string()),
193 application: tokens[1].to_string(),
194 },
195 3 => Self {
196 qualifier: Some(tokens[0].to_string()),
197 organization: Some(tokens[1].to_string()),
198 application: tokens[2].to_string(),
199 },
200 _ => Self {
201 qualifier: Some(tokens[0].to_string()),
202 organization: Some(tokens[1].to_string()),
203 application: tokens[2..].join("."),
204 },
205 };
206
207 Ok(label)
208 }
209}
210
211#[derive(Debug, Clone, PartialEq, Eq)]
213pub struct ServiceInstallCtx {
214 pub label: ServiceLabel,
218
219 pub program: PathBuf,
223
224 pub args: Vec<OsString>,
228
229 pub contents: Option<String>,
232
233 pub username: Option<String>,
237
238 pub working_directory: Option<PathBuf>,
240
241 pub environment: Option<Vec<(String, String)>>,
244
245 pub autostart: bool,
247
248 pub disable_restart_on_failure: bool,
252}
253
254impl ServiceInstallCtx {
255 pub fn cmd_iter(&self) -> impl Iterator<Item = &OsStr> {
257 std::iter::once(self.program.as_os_str()).chain(self.args_iter())
258 }
259
260 pub fn args_iter(&self) -> impl Iterator<Item = &OsStr> {
262 self.args.iter().map(OsString::as_os_str)
263 }
264}
265
266#[derive(Debug, Clone, PartialEq, Eq)]
268pub struct ServiceUninstallCtx {
269 pub label: ServiceLabel,
273}
274
275#[derive(Debug, Clone, PartialEq, Eq)]
277pub struct ServiceStartCtx {
278 pub label: ServiceLabel,
282}
283
284#[derive(Debug, Clone, PartialEq, Eq)]
286pub struct ServiceStopCtx {
287 pub label: ServiceLabel,
291}
292
293#[derive(Debug, Clone, PartialEq, Eq)]
295pub struct ServiceStatusCtx {
296 pub label: ServiceLabel,
300}
301
302#[cfg(test)]
303mod tests {
304 use super::*;
305
306 #[test]
307 fn test_service_label_parssing_1() {
308 let label = ServiceLabel::from_str("com.example.app123").unwrap();
309
310 assert_eq!(label.qualifier, Some("com".to_string()));
311 assert_eq!(label.organization, Some("example".to_string()));
312 assert_eq!(label.application, "app123".to_string());
313
314 assert_eq!(label.to_qualified_name(), "com.example.app123");
315 assert_eq!(label.to_script_name(), "example-app123");
316 }
317
318 #[test]
319 fn test_service_label_parssing_2() {
320 let label = ServiceLabel::from_str("example.app123").unwrap();
321
322 assert_eq!(label.qualifier, None);
323 assert_eq!(label.organization, Some("example".to_string()));
324 assert_eq!(label.application, "app123".to_string());
325
326 assert_eq!(label.to_qualified_name(), "example.app123");
327 assert_eq!(label.to_script_name(), "example-app123");
328 }
329
330 #[test]
331 fn test_service_label_parssing_3() {
332 let label = ServiceLabel::from_str("app123").unwrap();
333
334 assert_eq!(label.qualifier, None);
335 assert_eq!(label.organization, None);
336 assert_eq!(label.application, "app123".to_string());
337
338 assert_eq!(label.to_qualified_name(), "app123");
339 assert_eq!(label.to_script_name(), "app123");
340 }
341}