windows_services/
lib.rs

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/// The commands are sent by the service control manager to the service through the closure or callback
18/// passed to the service `run` method.
19#[derive(Debug, Copy, Clone, PartialEq, Eq)]
20pub enum Command {
21    /// The start command is sent when the service first starts.
22    Start,
23
24    /// The stop command is sent when the service is stopping just prior to process termination.
25    ///
26    /// This command will only be sent if the `can_stop` method is called as part of construction.
27    Stop,
28
29    /// The pause command is sent when the service is being paused but not stopping.
30    ///
31    /// This command will only be sent if the `can_pause` method is called as part of construction.
32    Pause,
33
34    /// The resume command is sent when the service is being resumed following a pause.
35    ///
36    /// This command will only be sent if the `can_pause` method is called as part of construction.
37    Resume,
38
39    /// An extended command.
40    ///
41    /// Specific commands will only be received if the `can_accept` methods is called to specify those
42    /// commands the service accepts.
43    Extended(ExtendedCommand),
44}
45
46/// A command not specifically covered by the `Command` enum.
47#[derive(Debug, Copy, Clone, PartialEq, Eq)]
48pub struct ExtendedCommand {
49    /// The control code for the command.
50    pub control: u32,
51
52    /// The event type, if any.
53    pub ty: u32,
54
55    /// The event data, if any.
56    pub data: *const c_void,
57}
58
59unsafe impl Send for ExtendedCommand {}
60unsafe impl Sync for ExtendedCommand {}
61
62/// Possible service states including transitional states.
63///
64/// This can be useful to query the current state with the `state` function or set the state with
65/// the `set_state` function.
66#[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
77/// A service builder, providing control over what commands the service supports before the service begins to run.
78pub 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    /// Creates a new `Service` object.
97    ///
98    /// By default, the service does not accept any service commands other than start.
99    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    /// The service accepts stop and shutdown commands.
118    pub fn can_stop(&mut self) -> &mut Self {
119        self.accept |= SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
120        self
121    }
122
123    /// The service accepts pause and resume commands.
124    pub fn can_pause(&mut self) -> &mut Self {
125        self.accept |= SERVICE_ACCEPT_PAUSE_CONTINUE;
126        self
127    }
128
129    /// The service accepts other specified commands.
130    pub fn can_accept(&mut self, accept: u32) -> &mut Self {
131        self.accept |= accept;
132        self
133    }
134
135    /// Runs the fallback closure if the service is not started by the Service Control Manager.
136    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    /// Runs the service with the given callback closure to receive commands sent by the service
142    /// control manager.
143    ///
144    /// This method will block for the life of the service. It will never return and immediately
145    /// terminate the current process after indicating to the service control manager that the
146    /// service has stopped.
147    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    /// Sets the current state of the service.
193    ///
194    /// In most cases, the service state is updated automatically and does not need to be set directly.
195    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        // Makes a copy to avoid holding a lock while calling `SetServiceStatus`.
208        let status: SERVICE_STATUS = *writer;
209        drop(writer);
210
211        unsafe {
212            SetServiceStatus(self.handle(), &status);
213        }
214    }
215
216    /// The raw handle representing the service.
217    pub fn handle(&self) -> *mut core::ffi::c_void {
218        *self.handle.read().unwrap()
219    }
220
221    /// The current state the service.
222    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    /// Sends the command to the service callback.
238    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    /// Low-level dispatcher to send control commands directly to the service.
244    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// Unlike `RegisterServiceCtrlHandlerExW`, `StartServiceCtrlDispatcherW` has no user-defined "context" parameter.
292// This lock allows us to safely retrieve the service instance from the service callback.
293#[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 {}