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#[derive(Debug, Copy, Clone, PartialEq, Eq)]
13pub enum Command {
14 Start,
16
17 Stop,
21
22 Pause,
26
27 Resume,
31}
32
33#[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
48pub 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
64pub 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 let status: SERVICE_STATUS = *writer;
81 drop(writer);
82
83 unsafe {
84 SetServiceStatus(HANDLE.get().unwrap().0, &status);
85 }
86}
87
88#[derive(Default)]
90pub struct Service(u32);
91
92impl Service {
93 pub fn new() -> Self {
97 Self(0)
98 }
99
100 pub fn can_stop(&mut self) -> &mut Self {
102 self.0 |= SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
103 self
104 }
105
106 pub fn can_pause(&mut self) -> &mut Self {
108 self.0 |= SERVICE_ACCEPT_PAUSE_CONTINUE;
109 self
110 }
111
112 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}