use std::{
mem, ptr,
thread::sleep,
time::{Duration, Instant},
};
use windows::{
Win32::{
Foundation::ERROR_SERVICE_ALREADY_RUNNING,
System::Services::{
CloseServiceHandle, OpenSCManagerW, OpenServiceW, QueryServiceStatusEx, SC_HANDLE,
SC_MANAGER_CONNECT, SC_STATUS_PROCESS_INFO, SERVICE_QUERY_CONFIG, SERVICE_QUERY_STATUS,
SERVICE_RUNNING, SERVICE_START, SERVICE_START_PENDING, SERVICE_STATUS_PROCESS,
SERVICE_STOPPED, StartServiceW,
},
},
core::PCWSTR,
};
use super::win32;
use crate::error::ElevateError;
const STOPPED_GRACE_TICKS: u32 = 3;
#[repr(C, align(8))]
struct AlignedStatusBuffer([u8; mem::size_of::<SERVICE_STATUS_PROCESS>()]);
pub struct ServiceManager(SC_HANDLE);
impl Drop for ServiceManager {
fn drop(&mut self) {
if !self.0.is_invalid() {
unsafe {
let _ = CloseServiceHandle(self.0);
}
}
}
}
impl ServiceManager {
pub fn open() -> Result<Self, ElevateError> {
let handle = win32!(OpenSCManagerW(
PCWSTR::null(),
PCWSTR::null(),
SC_MANAGER_CONNECT
))?;
Ok(Self(handle))
}
pub fn service(&self, name: PCWSTR) -> Result<Service, ElevateError> {
let handle = win32!(OpenServiceW(
self.0,
name,
SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG | SERVICE_START,
))?;
Ok(Service(handle))
}
}
pub struct Service(SC_HANDLE);
impl Drop for Service {
fn drop(&mut self) {
if !self.0.is_invalid() {
unsafe {
let _ = CloseServiceHandle(self.0);
}
}
}
}
impl Service {
pub fn start_and_wait(&self, name: &str, timeout: Duration) -> Result<u32, ElevateError> {
let mut deadline = Instant::now() + timeout;
let mut start_attempted = false;
let mut stopped_grace = 0u32;
let mut last_checkpoint = 0u32;
loop {
if Instant::now() > deadline {
return Err(ElevateError::ServiceTimeout {
name: name.to_string(),
seconds: timeout.as_secs(),
});
}
let mut raw_buf = AlignedStatusBuffer([0u8; mem::size_of::<SERVICE_STATUS_PROCESS>()]);
let mut bytes_needed = 0u32;
win32!(QueryServiceStatusEx(
self.0,
SC_STATUS_PROCESS_INFO,
Some(&mut raw_buf.0),
&raw mut bytes_needed,
))?;
let status =
unsafe { ptr::read_unaligned(raw_buf.0.as_ptr().cast::<SERVICE_STATUS_PROCESS>()) };
match status.dwCurrentState {
SERVICE_RUNNING => return Ok(status.dwProcessId),
SERVICE_STOPPED => {
if !start_attempted {
self.try_start()?;
start_attempted = true;
stopped_grace = STOPPED_GRACE_TICKS;
} else if stopped_grace > 0 {
stopped_grace -= 1;
} else {
return Err(ElevateError::ServiceFailed {
name: name.to_string(),
});
}
}
SERVICE_START_PENDING => {
stopped_grace = 0;
if status.dwCheckPoint > 0 && status.dwCheckPoint > last_checkpoint {
last_checkpoint = status.dwCheckPoint;
deadline = Instant::now() + timeout;
}
}
_ => {}
}
let wait_ms = (status.dwWaitHint / 10).clamp(1000, 3000);
sleep(Duration::from_millis(wait_ms as u64));
}
}
fn try_start(&self) -> Result<(), ElevateError> {
let res = win32!(StartServiceW(self.0, None));
match res {
Ok(()) => Ok(()),
Err(ElevateError::Win32 { ref source, .. })
if source.code() == ERROR_SERVICE_ALREADY_RUNNING.to_hresult() =>
{
Ok(())
}
Err(e) => Err(e),
}
}
}