1#![doc = include_str!("../readme.md")]
2#![cfg(windows)]
3#![expect(
4 non_camel_case_types,
5 non_snake_case,
6 clippy::needless_doctest_main,
7 clippy::upper_case_acronyms,
8 clippy::type_complexity
9)]
10
11mod bindings;
12use bindings::*;
13use std::boxed::Box;
14use std::ffi::c_void;
15use std::sync::RwLock;
16
17#[derive(Debug, Copy, Clone, PartialEq, Eq)]
20pub enum Command {
21 Start,
23
24 Stop,
28
29 Pause,
33
34 Resume,
38
39 Extended(ExtendedCommand),
44}
45
46#[derive(Debug, Copy, Clone, PartialEq, Eq)]
48pub struct ExtendedCommand {
49 pub control: u32,
51
52 pub ty: u32,
54
55 pub data: *const c_void,
57}
58
59unsafe impl Send for ExtendedCommand {}
60unsafe impl Sync for ExtendedCommand {}
61
62#[derive(Debug, Copy, Clone, PartialEq, Eq)]
67pub enum State {
68 ContinuePending,
69 Paused,
70 PausePending,
71 Running,
72 StartPending,
73 Stopped,
74 StopPending,
75}
76
77pub struct Service<'a> {
79 accept: u32,
80 fallback: Option<Box<dyn FnOnce(&Service) + 'a>>,
81 handle: RwLock<SERVICE_STATUS_HANDLE>,
82 callback: RwLock<Option<Box<dyn FnMut(&Service, Command) + Send + Sync + 'a>>>,
83 status: RwLock<SERVICE_STATUS>,
84}
85
86impl Default for Service<'_> {
87 fn default() -> Self {
88 Self::new()
89 }
90}
91
92unsafe impl Send for Service<'_> {}
93unsafe impl Sync for Service<'_> {}
94
95impl<'a> Service<'a> {
96 pub fn new() -> Self {
100 Self {
101 accept: 0,
102 fallback: None,
103 handle: RwLock::new(std::ptr::null_mut()),
104 callback: RwLock::new(None),
105 status: RwLock::new(SERVICE_STATUS {
106 dwServiceType: SERVICE_WIN32_OWN_PROCESS,
107 dwCurrentState: SERVICE_STOPPED,
108 dwControlsAccepted: 0,
109 dwWin32ExitCode: 0,
110 dwServiceSpecificExitCode: 0,
111 dwCheckPoint: 0,
112 dwWaitHint: 0,
113 }),
114 }
115 }
116
117 pub fn can_stop(&mut self) -> &mut Self {
119 self.accept |= SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
120 self
121 }
122
123 pub fn can_pause(&mut self) -> &mut Self {
125 self.accept |= SERVICE_ACCEPT_PAUSE_CONTINUE;
126 self
127 }
128
129 pub fn can_accept(&mut self, accept: u32) -> &mut Self {
131 self.accept |= accept;
132 self
133 }
134
135 pub fn can_fallback<F: FnOnce(&Service) + Send + 'a>(&mut self, f: F) -> &mut Self {
137 self.fallback = Some(Box::new(f));
138 self
139 }
140
141 pub fn run<F: FnMut(&Service, Command) + Send + Sync + 'a>(
148 &mut self,
149 callback: F,
150 ) -> Result<(), &'static str> {
151 debug_assert!(self.status.read().unwrap().dwCurrentState == SERVICE_STOPPED);
152 self.status.write().unwrap().dwControlsAccepted = self.accept;
153
154 {
155 let mut write = self.callback.write().unwrap();
156
157 if write.is_some() {
158 panic!("`run` was already called")
159 }
160
161 *write = Some(Box::new(callback));
162 }
163
164 let table = [
165 SERVICE_TABLE_ENTRYW {
166 lpServiceName: &mut 0,
167 lpServiceProc: Some(service_main),
168 },
169 SERVICE_TABLE_ENTRYW::default(),
170 ];
171
172 SERVICE_CONTEXT.write().unwrap().0 = self as *const _ as _;
173
174 let fallback = unsafe { StartServiceCtrlDispatcherW(table.as_ptr()) == 0 };
175
176 if fallback {
177 if let Some(fallback) = self.fallback.take() {
178 self.set_state(State::StartPending);
179 self.command(Command::Start);
180 self.set_state(State::Running);
181 fallback(self);
182 self.set_state(State::StopPending);
183 self.command(Command::Stop);
184 } else {
185 return Err("Use service control manager to start service");
186 }
187 }
188
189 Ok(())
190 }
191
192 pub fn set_state(&self, state: State) {
196 let mut writer = self.status.write().unwrap();
197 writer.dwCurrentState = match state {
198 State::ContinuePending => SERVICE_CONTINUE_PENDING,
199 State::Paused => SERVICE_PAUSED,
200 State::PausePending => SERVICE_PAUSE_PENDING,
201 State::Running => SERVICE_RUNNING,
202 State::StartPending => SERVICE_START_PENDING,
203 State::Stopped => SERVICE_STOPPED,
204 State::StopPending => SERVICE_STOP_PENDING,
205 };
206
207 let status: SERVICE_STATUS = *writer;
209 drop(writer);
210
211 unsafe {
212 SetServiceStatus(self.handle(), &status);
213 }
214 }
215
216 pub fn handle(&self) -> *mut core::ffi::c_void {
218 *self.handle.read().unwrap()
219 }
220
221 pub fn state(&self) -> State {
223 let reader = self.status.read().unwrap();
224
225 match reader.dwCurrentState {
226 SERVICE_CONTINUE_PENDING => State::ContinuePending,
227 SERVICE_PAUSED => State::Paused,
228 SERVICE_PAUSE_PENDING => State::PausePending,
229 SERVICE_RUNNING => State::Running,
230 SERVICE_START_PENDING => State::StartPending,
231 SERVICE_STOPPED => State::Stopped,
232 SERVICE_STOP_PENDING => State::StopPending,
233 _ => panic!("unexpected state"),
234 }
235 }
236
237 pub fn command(&self, command: Command) {
239 let mut write = self.callback.write().unwrap();
240 (write.as_deref_mut().unwrap())(self, command);
241 }
242
243 pub fn handler(&self, control: u32, event_type: u32, event_data: *const c_void) -> u32 {
245 handler(
246 control,
247 event_type,
248 event_data as *mut _,
249 self as *const _ as _,
250 )
251 }
252}
253
254extern "system" fn service_main(_len: u32, _args: *mut PWSTR) {
255 let service: &Service = unsafe { &*(SERVICE_CONTEXT.read().unwrap().0 as *const Service) };
256
257 *service.handle.write().unwrap() = unsafe {
258 RegisterServiceCtrlHandlerExW(std::ptr::null(), Some(handler), service as *const _ as _)
259 };
260
261 service.set_state(State::StartPending);
262 service.command(Command::Start);
263 service.set_state(State::Running);
264}
265
266extern "system" fn handler(control: u32, ty: u32, data: *mut c_void, context: *mut c_void) -> u32 {
267 let service = unsafe { &*(context as *const Service) };
268
269 match control {
270 SERVICE_CONTROL_CONTINUE if service.state() == State::Paused => {
271 service.set_state(State::ContinuePending);
272 service.command(Command::Resume);
273 service.set_state(State::Running);
274 }
275 SERVICE_CONTROL_PAUSE if service.state() == State::Running => {
276 service.set_state(State::PausePending);
277 service.command(Command::Pause);
278 service.set_state(State::Paused);
279 }
280 SERVICE_CONTROL_SHUTDOWN | SERVICE_CONTROL_STOP => {
281 service.set_state(State::StopPending);
282 service.command(Command::Stop);
283 service.set_state(State::Stopped);
284 }
285 _ => service.command(Command::Extended(ExtendedCommand { control, ty, data })),
286 }
287
288 NO_ERROR
289}
290
291#[derive(Debug)]
294struct ServiceContext(*const c_void);
295static SERVICE_CONTEXT: RwLock<ServiceContext> = RwLock::new(ServiceContext(std::ptr::null()));
296unsafe impl Send for ServiceContext {}
297unsafe impl Sync for ServiceContext {}