1use crate::utils::wrap_output;
2
3use super::{
4 ServiceInstallCtx, ServiceLevel, ServiceManager, ServiceStartCtx, ServiceStopCtx,
5 ServiceUninstallCtx,
6};
7use std::{
8 borrow::Cow,
9 ffi::{OsStr, OsString},
10 fmt, io,
11 process::{Command, Output, Stdio},
12};
13
14#[cfg(windows)]
15mod shell_escape;
16
17#[cfg(not(windows))]
18mod shell_escape {
19 use std::{borrow::Cow, ffi::OsStr};
20
21 pub fn escape(s: Cow<'_, OsStr>) -> Cow<'_, OsStr> {
23 s
24 }
25}
26
27static SC_EXE: &str = "sc.exe";
28
29#[derive(Clone, Debug, Default, PartialEq, Eq)]
31pub struct ScConfig {
32 pub install: ScInstallConfig,
33}
34
35#[derive(Clone, Debug, Default, PartialEq, Eq)]
37pub struct ScInstallConfig {
38 pub service_type: WindowsServiceType,
40
41 pub start_type: WindowsStartType,
43
44 pub error_severity: WindowsErrorSeverity,
46}
47
48#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
49pub enum WindowsServiceType {
50 Own,
52
53 Share,
55
56 Kernel,
58
59 FileSys,
61
62 Rec,
64}
65
66impl Default for WindowsServiceType {
67 fn default() -> Self {
68 Self::Own
69 }
70}
71
72impl fmt::Display for WindowsServiceType {
73 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74 match self {
75 Self::Own => write!(f, "own"),
76 Self::Share => write!(f, "share"),
77 Self::Kernel => write!(f, "kernel"),
78 Self::FileSys => write!(f, "filesys"),
79 Self::Rec => write!(f, "rec"),
80 }
81 }
82}
83
84#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
85pub enum WindowsStartType {
86 Boot,
88
89 System,
91
92 Auto,
95
96 Demand,
98
99 Disabled,
102}
103
104impl Default for WindowsStartType {
105 fn default() -> Self {
106 Self::Auto
107 }
108}
109
110impl fmt::Display for WindowsStartType {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 match self {
113 Self::Boot => write!(f, "boot"),
114 Self::System => write!(f, "system"),
115 Self::Auto => write!(f, "auto"),
116 Self::Demand => write!(f, "demand"),
117 Self::Disabled => write!(f, "disabled"),
118 }
119 }
120}
121
122#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
123pub enum WindowsErrorSeverity {
124 Normal,
126
127 Severe,
131
132 Critical,
136
137 Ignore,
140}
141
142impl Default for WindowsErrorSeverity {
143 fn default() -> Self {
144 Self::Normal
145 }
146}
147
148impl fmt::Display for WindowsErrorSeverity {
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150 match self {
151 Self::Normal => write!(f, "normal"),
152 Self::Severe => write!(f, "severe"),
153 Self::Critical => write!(f, "critical"),
154 Self::Ignore => write!(f, "ignore"),
155 }
156 }
157}
158
159#[derive(Clone, Debug, Default, PartialEq, Eq)]
162pub struct ScServiceManager {
163 pub config: ScConfig,
165}
166
167impl ScServiceManager {
168 pub fn system() -> Self {
170 Self::default()
171 }
172
173 pub fn with_config(self, config: ScConfig) -> Self {
175 Self { config }
176 }
177}
178
179impl ServiceManager for ScServiceManager {
180 fn available(&self) -> io::Result<bool> {
181 match which::which(SC_EXE) {
182 Ok(_) => Ok(true),
183 Err(which::Error::CannotFindBinaryPath) => Ok(false),
184 Err(x) => Err(io::Error::new(io::ErrorKind::Other, x)),
185 }
186 }
187
188 fn install(&self, ctx: ServiceInstallCtx) -> io::Result<()> {
189 let service_name = ctx.label.to_qualified_name();
190
191 let service_type = OsString::from(self.config.install.service_type.to_string());
192 let error_severity = OsString::from(self.config.install.error_severity.to_string());
193 let start_type = if ctx.autostart {
194 OsString::from("Auto")
195 } else {
196 OsString::from(self.config.install.start_type.to_string())
200 };
201
202 let mut binpath = OsString::new();
204 binpath.push(shell_escape::escape(Cow::Borrowed(ctx.program.as_ref())));
205 for arg in ctx.args_iter() {
206 binpath.push(" ");
207 binpath.push(shell_escape::escape(Cow::Borrowed(arg)));
208 }
209
210 let display_name = OsStr::new(&service_name);
211
212 wrap_output(sc_exe(
213 "create",
214 &service_name,
215 [
216 OsStr::new("type="),
218 service_type.as_os_str(),
219 OsStr::new("start="),
221 start_type.as_os_str(),
222 OsStr::new("error="),
224 error_severity.as_os_str(),
225 OsStr::new("binpath="),
227 binpath.as_os_str(),
228 OsStr::new("displayname="),
230 display_name,
231 ],
232 )?)?;
233 Ok(())
234 }
235
236 fn uninstall(&self, ctx: ServiceUninstallCtx) -> io::Result<()> {
237 let service_name = ctx.label.to_qualified_name();
238 wrap_output(sc_exe("delete", &service_name, [])?)?;
239 Ok(())
240 }
241
242 fn start(&self, ctx: ServiceStartCtx) -> io::Result<()> {
243 let service_name = ctx.label.to_qualified_name();
244 wrap_output(sc_exe("start", &service_name, [])?)?;
245 Ok(())
246 }
247
248 fn stop(&self, ctx: ServiceStopCtx) -> io::Result<()> {
249 let service_name = ctx.label.to_qualified_name();
250 wrap_output(sc_exe("stop", &service_name, [])?)?;
251 Ok(())
252 }
253
254 fn level(&self) -> ServiceLevel {
255 ServiceLevel::System
256 }
257
258 fn set_level(&mut self, level: ServiceLevel) -> io::Result<()> {
259 match level {
260 ServiceLevel::System => Ok(()),
261 ServiceLevel::User => Err(io::Error::new(
262 io::ErrorKind::Unsupported,
263 "sc.exe does not support user-level services",
264 )),
265 }
266 }
267
268 fn status(&self, ctx: crate::ServiceStatusCtx) -> io::Result<crate::ServiceStatus> {
269 let service_name = ctx.label.to_qualified_name();
270 let output = sc_exe("query", &service_name, [])?;
271 if !output.status.success() {
272 if matches!(output.status.code(), Some(1060)) {
273 return Ok(crate::ServiceStatus::NotInstalled);
275 }
276 return Err(io::Error::new(
277 io::ErrorKind::Other,
278 format!(
279 "Command failed with exit code {}: {}",
280 output.status.code().unwrap_or(-1),
281 String::from_utf8_lossy(&output.stderr)
282 ),
283 ));
284 }
285
286 let stdout = String::from_utf8_lossy(&output.stdout);
287 let line = stdout.split('\n').find(|line| {
288 line.trim_matches(&['\r', ' '])
289 .to_lowercase()
290 .starts_with("state")
291 });
292 let status = match line {
293 Some(line) if line.contains("RUNNING") => crate::ServiceStatus::Running,
294 _ => crate::ServiceStatus::Stopped(None), };
296 Ok(status)
297 }
298}
299
300fn sc_exe<'a>(
301 cmd: &str,
302 service_name: &str,
303 args: impl IntoIterator<Item = &'a OsStr>,
304) -> io::Result<Output> {
305 let mut command = Command::new(SC_EXE);
306
307 command
308 .stdin(Stdio::null())
309 .stdout(Stdio::piped())
310 .stderr(Stdio::piped());
311
312 command.arg(cmd).arg(service_name);
313
314 for arg in args {
315 command.arg(arg);
316 }
317
318 command.output()
319}