1use crate::utils::wrap_output;
2
3use super::{
4 RestartPolicy, ServiceInstallCtx, ServiceLevel, ServiceManager, ServiceStartCtx,
5 ServiceStopCtx, 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 match ctx.restart_policy {
192 RestartPolicy::Never => {
193 }
195 RestartPolicy::Always { .. } | RestartPolicy::OnFailure { .. } | RestartPolicy::OnSuccess { .. } => {
196 log::warn!(
197 "sc.exe does not support automatic restart policies through 'sc create'; service '{}' will not restart automatically. Use 'sc failure' to configure restart behavior manually.",
198 ctx.label.to_qualified_name()
199 );
200 }
201 }
202
203 let service_name = ctx.label.to_qualified_name();
204
205 let service_type = OsString::from(self.config.install.service_type.to_string());
206 let error_severity = OsString::from(self.config.install.error_severity.to_string());
207 let start_type = if ctx.autostart {
208 OsString::from("Auto")
209 } else {
210 OsString::from(self.config.install.start_type.to_string())
214 };
215
216 let mut binpath = OsString::new();
218 binpath.push(shell_escape::escape(Cow::Borrowed(ctx.program.as_ref())));
219 for arg in ctx.args_iter() {
220 binpath.push(" ");
221 binpath.push(shell_escape::escape(Cow::Borrowed(arg)));
222 }
223
224 let display_name = OsStr::new(&service_name);
225
226 wrap_output(sc_exe(
227 "create",
228 &service_name,
229 [
230 OsStr::new("type="),
232 service_type.as_os_str(),
233 OsStr::new("start="),
235 start_type.as_os_str(),
236 OsStr::new("error="),
238 error_severity.as_os_str(),
239 OsStr::new("binpath="),
241 binpath.as_os_str(),
242 OsStr::new("displayname="),
244 display_name,
245 ],
246 )?)?;
247 Ok(())
248 }
249
250 fn uninstall(&self, ctx: ServiceUninstallCtx) -> io::Result<()> {
251 let service_name = ctx.label.to_qualified_name();
252 wrap_output(sc_exe("delete", &service_name, [])?)?;
253 Ok(())
254 }
255
256 fn start(&self, ctx: ServiceStartCtx) -> io::Result<()> {
257 let service_name = ctx.label.to_qualified_name();
258 wrap_output(sc_exe("start", &service_name, [])?)?;
259 Ok(())
260 }
261
262 fn stop(&self, ctx: ServiceStopCtx) -> io::Result<()> {
263 let service_name = ctx.label.to_qualified_name();
264 wrap_output(sc_exe("stop", &service_name, [])?)?;
265 Ok(())
266 }
267
268 fn level(&self) -> ServiceLevel {
269 ServiceLevel::System
270 }
271
272 fn set_level(&mut self, level: ServiceLevel) -> io::Result<()> {
273 match level {
274 ServiceLevel::System => Ok(()),
275 ServiceLevel::User => Err(io::Error::new(
276 io::ErrorKind::Unsupported,
277 "sc.exe does not support user-level services",
278 )),
279 }
280 }
281
282 fn status(&self, ctx: crate::ServiceStatusCtx) -> io::Result<crate::ServiceStatus> {
283 let service_name = ctx.label.to_qualified_name();
284 let output = sc_exe("query", &service_name, [])?;
285 if !output.status.success() {
286 if matches!(output.status.code(), Some(1060)) {
287 return Ok(crate::ServiceStatus::NotInstalled);
289 }
290 return Err(io::Error::new(
291 io::ErrorKind::Other,
292 format!(
293 "Command failed with exit code {}: {}",
294 output.status.code().unwrap_or(-1),
295 String::from_utf8_lossy(&output.stderr)
296 ),
297 ));
298 }
299
300 let stdout = String::from_utf8_lossy(&output.stdout);
301 let line = stdout.split('\n').find(|line| {
302 line.trim_matches(&['\r', ' '])
303 .to_lowercase()
304 .starts_with("state")
305 });
306 let status = match line {
307 Some(line) if line.contains("RUNNING") => crate::ServiceStatus::Running,
308 _ => crate::ServiceStatus::Stopped(None), };
310 Ok(status)
311 }
312}
313
314fn sc_exe<'a>(
315 cmd: &str,
316 service_name: &str,
317 args: impl IntoIterator<Item = &'a OsStr>,
318) -> io::Result<Output> {
319 let mut command = Command::new(SC_EXE);
320
321 command
322 .stdin(Stdio::null())
323 .stdout(Stdio::piped())
324 .stderr(Stdio::piped());
325
326 command.arg(cmd).arg(service_name);
327
328 for arg in args {
329 command.arg(arg);
330 }
331
332 command.output()
333}