Skip to main content

windows_erg/service/
service.rs

1use std::time::{Duration, Instant};
2
3use windows::Win32::Foundation::ERROR_SERVICE_DOES_NOT_EXIST;
4use windows::Win32::System::Services::{
5    CloseServiceHandle, ControlService, QueryServiceStatusEx, SC_HANDLE, SC_STATUS_PROCESS_INFO,
6    SERVICE_STATUS, SERVICE_STATUS_PROCESS, StartServiceW,
7};
8use windows::core::PCWSTR;
9
10use super::status::ServiceStatus;
11use super::types::{ServiceAccess, ServiceControl, ServiceState};
12use crate::Result;
13use crate::error::{
14    Error, ServiceError, ServiceInvalidStateError, ServiceNotFoundError, ServiceOperationError,
15};
16
17/// Handle to an opened Windows service.
18pub struct Service {
19    handle: SC_HANDLE,
20    name: String,
21}
22
23impl Service {
24    pub(crate) fn new(handle: SC_HANDLE, name: String) -> Self {
25        Service { handle, name }
26    }
27
28    /// Open a service by name using default access rights.
29    pub fn open(name: &str) -> Result<Self> {
30        super::manager::ServiceManager::connect()?
31            .open_with_access(name, ServiceAccess::QueryStatus)
32    }
33
34    /// Open a service by name with explicit access rights.
35    pub fn open_with_access(name: &str, access: ServiceAccess) -> Result<Self> {
36        super::manager::ServiceManager::connect()?.open_with_access(name, access)
37    }
38
39    /// Service key name.
40    pub fn name(&self) -> &str {
41        self.name.as_str()
42    }
43
44    /// Query current service status.
45    pub fn query(&self) -> Result<ServiceStatus> {
46        let mut work_buffer = Vec::with_capacity(std::mem::size_of::<SERVICE_STATUS_PROCESS>());
47        self.query_with_buffer(&mut work_buffer)
48    }
49
50    /// Query current service status using a reusable workspace buffer.
51    pub fn query_with_buffer(&self, work_buffer: &mut Vec<u8>) -> Result<ServiceStatus> {
52        work_buffer.clear();
53
54        let mut bytes_needed = 0u32;
55        let _ = unsafe {
56            QueryServiceStatusEx(self.handle, SC_STATUS_PROCESS_INFO, None, &mut bytes_needed)
57        };
58
59        let required = (bytes_needed as usize).max(std::mem::size_of::<SERVICE_STATUS_PROCESS>());
60        if work_buffer.len() < required {
61            work_buffer.resize(required, 0);
62        }
63
64        unsafe {
65            QueryServiceStatusEx(
66                self.handle,
67                SC_STATUS_PROCESS_INFO,
68                Some(work_buffer),
69                &mut bytes_needed,
70            )
71        }
72        .map_err(|e| {
73            if e.code().0 == ERROR_SERVICE_DOES_NOT_EXIST.to_hresult().0 {
74                return Error::Service(ServiceError::NotFound(ServiceNotFoundError::with_code(
75                    self.name.clone(),
76                    e.code().0,
77                )));
78            }
79
80            Error::Service(ServiceError::OperationFailed(
81                ServiceOperationError::with_code(
82                    self.name.clone(),
83                    "query",
84                    "QueryServiceStatusEx failed",
85                    e.code().0,
86                ),
87            ))
88        })?;
89
90        let raw = unsafe { &*(work_buffer.as_ptr() as *const SERVICE_STATUS_PROCESS) };
91        Ok(ServiceStatus::from_status_process(
92            self.name.clone(),
93            None,
94            raw,
95        ))
96    }
97
98    /// Start the service.
99    pub fn start(&self) -> Result<()> {
100        unsafe { StartServiceW(self.handle, None) }.map_err(|e| {
101            Error::Service(ServiceError::OperationFailed(
102                ServiceOperationError::with_code(
103                    self.name.clone(),
104                    "start",
105                    "StartServiceW failed",
106                    e.code().0,
107                ),
108            ))
109        })
110    }
111
112    /// Stop the service.
113    pub fn stop(&self) -> Result<()> {
114        self.send_control(ServiceControl::Stop)
115    }
116
117    /// Send a control code to the service.
118    pub fn send_control(&self, control: ServiceControl) -> Result<()> {
119        let mut status = SERVICE_STATUS::default();
120        unsafe { ControlService(self.handle, control.to_windows(), &mut status) }.map_err(|e| {
121            Error::Service(ServiceError::OperationFailed(
122                ServiceOperationError::with_code(
123                    self.name.clone(),
124                    control.operation_name(),
125                    "ControlService failed",
126                    e.code().0,
127                ),
128            ))
129        })
130    }
131
132    /// Restart the service by waiting for stopped state and then starting it.
133    pub fn restart(&self, timeout: Duration) -> Result<()> {
134        self.stop()?;
135        self.wait_for_state(ServiceState::Stopped, timeout)?;
136        self.start()
137    }
138
139    fn wait_for_state(&self, desired: ServiceState, timeout: Duration) -> Result<()> {
140        let start = Instant::now();
141        let mut work_buffer = Vec::with_capacity(std::mem::size_of::<SERVICE_STATUS_PROCESS>());
142        let mut last_checkpoint = 0u32;
143        let mut checkpoint_stale_since = Instant::now();
144
145        while start.elapsed() <= timeout {
146            let status = self.query_with_buffer(&mut work_buffer)?;
147            if status.state == desired {
148                return Ok(());
149            }
150
151            if status.checkpoint != last_checkpoint {
152                last_checkpoint = status.checkpoint;
153                checkpoint_stale_since = Instant::now();
154            }
155
156            if checkpoint_stale_since.elapsed() > timeout {
157                break;
158            }
159
160            let wait_hint_ms = status.wait_hint_ms.clamp(100, 10_000);
161            let poll_ms = (wait_hint_ms / 10).clamp(100, 1000);
162            std::thread::sleep(Duration::from_millis(poll_ms as u64));
163        }
164
165        Err(Error::Service(ServiceError::InvalidState(
166            ServiceInvalidStateError::new(
167                self.name.clone(),
168                desired.as_str(),
169                "timed out waiting for state transition",
170            ),
171        )))
172    }
173}
174
175impl Drop for Service {
176    fn drop(&mut self) {
177        let _ = unsafe { CloseServiceHandle(self.handle) };
178    }
179}
180
181pub(crate) fn as_pcwstr(wide: &[u16]) -> PCWSTR {
182    PCWSTR(wide.as_ptr())
183}