windows_services/
lib.rs

1#![doc = include_str!("../readme.md")]
2#![cfg(windows)]
3#![allow(clippy::needless_doctest_main)]
4
5mod bindings;
6use bindings::*;
7use std::boxed::Box;
8use std::sync::{OnceLock, RwLock};
9
10/// The commands are sent by the service control manager to the service through the closure or callback
11/// passed to the service `run` method.
12#[derive(Debug, Copy, Clone, PartialEq, Eq)]
13pub enum Command {
14    /// The start command is sent when the service first starts.
15    Start,
16
17    /// The stop command is sent when the service is stopping just prior to process termination.
18    ///
19    /// This command will only be sent if the `can_stop` method is called as part of construction.
20    Stop,
21
22    /// The pause command is sent when the service is being paused but not stopping.
23    ///
24    /// This command will only be sent if the `can_pause` method is called as part of construction.
25    Pause,
26
27    /// The resume command is sent when the service is being resumed following a pause.
28    ///
29    /// This command will only be sent if the `can_pause` method is called as part of construction.
30    Resume,
31}
32
33/// Possible service states including transitional states.
34///
35/// This can be useful to query the current state with the `state` function or set the state with
36/// the `set_state` function.
37#[derive(Debug, Copy, Clone, PartialEq, Eq)]
38pub enum State {
39    ContinuePending,
40    Paused,
41    PausePending,
42    Running,
43    StartPending,
44    Stopped,
45    StopPending,
46}
47
48/// The current state the service.
49pub fn state() -> State {
50    let reader = STATUS.read().unwrap();
51
52    match reader.dwCurrentState {
53        SERVICE_CONTINUE_PENDING => State::ContinuePending,
54        SERVICE_PAUSED => State::Paused,
55        SERVICE_PAUSE_PENDING => State::PausePending,
56        SERVICE_RUNNING => State::Running,
57        SERVICE_START_PENDING => State::StartPending,
58        SERVICE_STOPPED => State::Stopped,
59        SERVICE_STOP_PENDING => State::StopPending,
60        _ => panic!("unexpected state"),
61    }
62}
63
64/// Sets the current state of the service.
65///
66/// In most cases, the service state is updated automatically and does not need to be set directly.
67pub fn set_state(state: State) {
68    let mut writer = STATUS.write().unwrap();
69    writer.dwCurrentState = match state {
70        State::ContinuePending => SERVICE_CONTINUE_PENDING,
71        State::Paused => SERVICE_PAUSED,
72        State::PausePending => SERVICE_PAUSE_PENDING,
73        State::Running => SERVICE_RUNNING,
74        State::StartPending => SERVICE_START_PENDING,
75        State::Stopped => SERVICE_STOPPED,
76        State::StopPending => SERVICE_STOP_PENDING,
77    };
78
79    // Makes a copy to avoid holding a lock while calling `SetServiceStatus`.
80    let status: SERVICE_STATUS = *writer;
81    drop(writer);
82
83    unsafe {
84        SetServiceStatus(HANDLE.get().unwrap().0, &status);
85    }
86}
87
88/// A service builder, providing control over what commands the service supports before the service begins to run.
89#[derive(Default)]
90pub struct Service(u32);
91
92impl Service {
93    /// Creates a new `Service` object.
94    ///
95    /// By default, the service does not accept any service commands other than start.
96    pub fn new() -> Self {
97        Self(0)
98    }
99
100    /// The service accepts stop and shutdown commands.
101    pub fn can_stop(&mut self) -> &mut Self {
102        self.0 |= SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
103        self
104    }
105
106    /// The service accepts pause and resume commands.
107    pub fn can_pause(&mut self) -> &mut Self {
108        self.0 |= SERVICE_ACCEPT_PAUSE_CONTINUE;
109        self
110    }
111
112    /// Runs the service with the given callback closure to receive commands sent by the service
113    /// control manager.
114    ///
115    /// This method will block for the life of the service. It will never return and immediately
116    /// terminate the current process after indicating to the service control manager that the
117    /// service has stopped.
118    pub fn run<F: FnMut(Command) + Send + Sync>(&self, callback: F) -> ! {
119        STATUS.write().unwrap().dwControlsAccepted = self.0;
120        CALLBACK
121            .set(Callback(Box::into_raw(Box::new(callback)) as *mut _))
122            .unwrap();
123
124        let table = [
125            SERVICE_TABLE_ENTRYW {
126                lpServiceName: &mut 0,
127                lpServiceProc: Some(service_main),
128            },
129            SERVICE_TABLE_ENTRYW::default(),
130        ];
131
132        if unsafe { StartServiceCtrlDispatcherW(table.as_ptr()) } == 0 {
133            println!(
134                r#"Use service control manager to start service.
135    
136Install:
137    > sc create ServiceName binPath= "{}"
138
139Start:
140    > sc start ServiceName
141
142Query status:
143    > sc query ServiceName
144
145Stop:
146    > sc stop ServiceName
147
148Delete (uninstall):
149    > sc delete ServiceName
150"#,
151                std::env::current_exe().unwrap().display()
152            );
153        }
154
155        std::process::exit(0);
156    }
157}
158
159#[derive(Debug)]
160struct Handle(SERVICE_STATUS_HANDLE);
161static HANDLE: OnceLock<Handle> = OnceLock::new();
162unsafe impl Send for Handle {}
163unsafe impl Sync for Handle {}
164
165#[derive(Debug)]
166struct Callback(*mut (dyn FnMut(Command) + Send + Sync));
167static CALLBACK: OnceLock<Callback> = OnceLock::new();
168unsafe impl Send for Callback {}
169unsafe impl Sync for Callback {}
170
171static STATUS: RwLock<SERVICE_STATUS> = RwLock::new(SERVICE_STATUS {
172    dwServiceType: SERVICE_WIN32_OWN_PROCESS,
173    dwCurrentState: SERVICE_STOPPED,
174    dwControlsAccepted: 0,
175    dwWin32ExitCode: 0,
176    dwServiceSpecificExitCode: 0,
177    dwCheckPoint: 0,
178    dwWaitHint: 0,
179});
180
181fn callback(command: Command) {
182    unsafe {
183        (*CALLBACK.get().unwrap().0)(command);
184    }
185}
186
187extern "system" fn service_main(_len: u32, _args: *mut PWSTR) {
188    let handle = unsafe { RegisterServiceCtrlHandlerW(std::ptr::null(), Some(handler)) };
189
190    HANDLE.set(Handle(handle)).unwrap();
191    set_state(State::StartPending);
192    callback(Command::Start);
193    set_state(State::Running);
194}
195
196extern "system" fn handler(control: u32) {
197    match control {
198        SERVICE_CONTROL_CONTINUE if state() == State::Paused => {
199            set_state(State::ContinuePending);
200            callback(Command::Resume);
201            set_state(State::Running);
202        }
203        SERVICE_CONTROL_PAUSE if state() == State::Running => {
204            set_state(State::PausePending);
205            callback(Command::Pause);
206            set_state(State::Paused);
207        }
208        SERVICE_CONTROL_SHUTDOWN | SERVICE_CONTROL_STOP => {
209            set_state(State::StopPending);
210            callback(Command::Stop);
211            set_state(State::Stopped);
212        }
213        _ => {}
214    }
215}