use std::{
mem, ptr,
time::{Duration, Instant},
};
use windows_sys::Win32::System::Services::{
CloseServiceHandle, ControlService, OpenSCManagerW, OpenServiceW, QueryServiceStatus,
SC_MANAGER_CONNECT, SERVICE_CONTROL_STOP, SERVICE_QUERY_STATUS, SERVICE_RUNNING, SERVICE_START,
SERVICE_STATUS, SERVICE_STOP, SERVICE_STOP_PENDING, SERVICE_STOPPED, StartServiceW,
};
use crate::{
error::{Error, Result},
wide::wide,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ServiceState {
Running,
Stopped,
Stopping,
Other(u32),
}
pub fn service_state(name: &str) -> Result<ServiceState> {
let service = Service::open(name, SERVICE_QUERY_STATUS)?;
Ok(state_of(service.status()?))
}
pub fn stop_service(name: &str, timeout: Duration) -> Result<()> {
let service = match Service::open(name, SERVICE_STOP | SERVICE_QUERY_STATUS) {
Ok(service) => service,
Err(error) if error.is_not_found() => return Ok(()),
Err(error) => return Err(error),
};
if state_of(service.status()?) == ServiceState::Stopped {
return Ok(());
}
let mut status: SERVICE_STATUS = unsafe { mem::zeroed() };
if unsafe { ControlService(service.handle, SERVICE_CONTROL_STOP, &mut status) } == 0 {
let error = Error::last("ControlService");
if state_of(service.status()?) != ServiceState::Stopped {
return Err(error);
}
return Ok(());
}
let deadline = Instant::now() + timeout;
loop {
match state_of(service.status()?) {
ServiceState::Stopped => return Ok(()),
_ if Instant::now() >= deadline => {
return Err(Error::saying(
"stop_service",
format!("{name} did not stop within {} seconds", timeout.as_secs()),
));
}
_ => std::thread::sleep(Duration::from_millis(100)),
}
}
}
pub fn start_service(name: &str) -> Result<()> {
let service = Service::open(name, SERVICE_START | SERVICE_QUERY_STATUS)?;
if state_of(service.status()?) == ServiceState::Running {
return Ok(());
}
if unsafe { StartServiceW(service.handle, 0, ptr::null()) } == 0 {
return Err(Error::last("StartServiceW"));
}
Ok(())
}
fn state_of(status: SERVICE_STATUS) -> ServiceState {
match status.dwCurrentState {
SERVICE_RUNNING => ServiceState::Running,
SERVICE_STOPPED => ServiceState::Stopped,
SERVICE_STOP_PENDING => ServiceState::Stopping,
other => ServiceState::Other(other),
}
}
struct Service {
handle: windows_sys::Win32::System::Services::SC_HANDLE,
_manager: Manager,
}
struct Manager(windows_sys::Win32::System::Services::SC_HANDLE);
impl Service {
fn open(name: &str, access: u32) -> Result<Self> {
let manager = unsafe { OpenSCManagerW(ptr::null(), ptr::null(), SC_MANAGER_CONNECT) };
if manager.is_null() {
return Err(Error::last("OpenSCManagerW"));
}
let manager = Manager(manager);
let name = wide(name);
let service = unsafe { OpenServiceW(manager.0, name.as_ptr(), access) };
if service.is_null() {
return Err(Error::last("OpenServiceW"));
}
Ok(Self {
handle: service,
_manager: manager,
})
}
fn status(&self) -> Result<SERVICE_STATUS> {
let mut status: SERVICE_STATUS = unsafe { mem::zeroed() };
if unsafe { QueryServiceStatus(self.handle, &mut status) } == 0 {
return Err(Error::last("QueryServiceStatus"));
}
Ok(status)
}
}
impl Drop for Service {
fn drop(&mut self) {
unsafe { CloseServiceHandle(self.handle) };
}
}
impl Drop for Manager {
fn drop(&mut self) {
unsafe { CloseServiceHandle(self.0) };
}
}