use std::path::PathBuf;
use std::os::windows::ffi::OsStrExt;
use std::sync::{Arc, Mutex};
use winapi::shared::minwindef::DWORD;
use winapi::um::winsvc::CloseServiceHandle;
use winapi::um::winsvc::OpenSCManagerW;
use winapi::um::winsvc::OpenServiceW;
use winapi::um::winsvc::QueryServiceStatus;
use winapi::um::winsvc::StartServiceW;
use winapi::um::winsvc::SC_HANDLE;
use winapi::um::winsvc::SERVICE_RUNNING;
use winapi::um::winsvc::SERVICE_START_PENDING;
pub struct ServiceStatusHandle(winapi::um::winsvc::SERVICE_STATUS_HANDLE);
#[derive(Debug)]
pub enum StartServiceError {
WindowsError(DWORD),
FailedToStart(DWORD),
}
#[derive(Debug)]
pub enum CreateServiceError {
WindowsError(DWORD),
}
impl ServiceStatusHandle {
pub fn new(a: winapi::um::winsvc::SERVICE_STATUS_HANDLE) -> Self {
Self(a)
}
}
unsafe impl Send for ServiceStatusHandle {}
lazy_static::lazy_static! {
pub static ref SERVICE_HANDLE : Arc<Mutex<ServiceStatusHandle>> = Arc::new(Mutex::new(ServiceStatusHandle(std::ptr::null_mut())));
}
pub type DispatchFn =
extern "system" fn(winapi::shared::minwindef::DWORD, *mut winapi::um::winnt::LPWSTR);
pub type ServiceFn<T> = fn(
rx: Option<std::sync::mpsc::Receiver<crate::ServiceEvent<T>>>,
tx: Option<std::sync::mpsc::Sender<crate::ServiceEvent<T>>>,
);
pub type ServiceFnAsync<T> = fn(
rx: std::sync::mpsc::Receiver<crate::ServiceEvent<T>>,
tx: std::sync::mpsc::Sender<crate::ServiceEvent<T>>,
args: Vec<String>,
standalone_mode: bool,
) -> u32;
#[derive(Debug)]
pub struct Session(u32);
pub fn get_utf16(value: &str) -> Vec<u16> {
std::ffi::OsStr::new(value)
.encode_wide()
.chain(std::iter::once(0))
.collect()
}
pub unsafe fn convert_args(
argc: winapi::shared::minwindef::DWORD,
argv: *mut winapi::um::winnt::LPWSTR,
) -> Vec<String> {
let mut args = Vec::new();
for i in 0..argc {
let s = *argv.add(i as usize);
let widestr = widestring::WideCString::from_ptr_str(s);
args.push(widestr.to_string_lossy());
}
args
}
pub fn get_optional_utf16(value: Option<&str>) -> winapi::um::winnt::LPCWSTR {
if let Some(s) = value {
get_utf16(s).as_ptr()
} else {
std::ptr::null_mut()
}
}
pub struct ServiceHandle {
handle: SC_HANDLE,
}
impl ServiceHandle {
pub fn get_handle(&self) -> SC_HANDLE {
self.handle
}
}
impl Drop for ServiceHandle {
fn drop(&mut self) {
if !self.handle.is_null() {
unsafe { CloseServiceHandle(self.handle) };
}
}
}
pub struct ServiceController {
handle: SC_HANDLE,
}
impl ServiceController {
pub fn get_handle(&self) -> SC_HANDLE {
self.handle
}
pub fn open(access: DWORD) -> Result<Self, DWORD> {
let handle = unsafe { OpenSCManagerW(std::ptr::null_mut(), std::ptr::null_mut(), access) };
if handle.is_null() {
Err(unsafe { winapi::um::errhandlingapi::GetLastError() })
} else {
Ok(Self { handle })
}
}
pub fn open_service(&self, name: &str, access: DWORD) -> Result<ServiceHandle, DWORD> {
let handle = unsafe { OpenServiceW(self.handle, get_utf16(name).as_ptr(), access) };
if handle.is_null() {
Err(unsafe { winapi::um::errhandlingapi::GetLastError() })
} else {
Ok(ServiceHandle { handle })
}
}
}
pub struct ServiceConfig {
arguments: Vec<String>,
description: String,
binary: PathBuf,
username: Option<String>,
pub display: String,
pub user_password: Option<String>,
pub desired_access: DWORD,
pub service_type: DWORD,
pub start_type: DWORD,
pub error_control: DWORD,
pub tag_id: DWORD,
pub load_order_group: Option<String>,
pub dependencies: Option<String>,
}
impl ServiceConfig {
pub fn new(
arguments: Vec<String>,
description: String,
binary: PathBuf,
username: Option<String>,
) -> Self {
Self {
arguments,
description,
binary,
username,
display: String::new(),
user_password: None,
desired_access: winapi::um::winsvc::SERVICE_ALL_ACCESS,
service_type: winapi::um::winnt::SERVICE_WIN32_OWN_PROCESS,
start_type: winapi::um::winnt::SERVICE_AUTO_START,
error_control: winapi::um::winnt::SERVICE_ERROR_NORMAL,
tag_id: 0,
load_order_group: None,
dependencies: None,
}
}
}
pub struct Service {
name: String,
}
impl Service {
pub fn new(name: String) -> Self {
Self { name }
}
pub fn new_log(&self, level: super::LogLevel) {
eventlog::init(&format!("{} Log", self.name), level.level()).unwrap();
}
pub fn exists(&self) -> bool {
let service_manager = ServiceController::open(winapi::um::winsvc::SC_MANAGER_ALL_ACCESS)
.unwrap_or_else(|e| panic!("Unable to get service controller {}", e)); let service =
service_manager.open_service(&self.name, winapi::um::winsvc::SERVICE_ALL_ACCESS);
service.is_ok()
}
pub fn stop(&mut self) -> Result<(), DWORD> {
let service_manager = ServiceController::open(winapi::um::winsvc::SC_MANAGER_ALL_ACCESS)?; let service = service_manager
.open_service(&self.name, winapi::um::winsvc::SERVICE_ALL_ACCESS)
.unwrap();
let mut service_status: winapi::um::winsvc::SERVICE_STATUS =
winapi::um::winsvc::SERVICE_STATUS {
dwServiceType: winapi::um::winnt::SERVICE_WIN32_OWN_PROCESS,
dwCurrentState: winapi::um::winsvc::SERVICE_STOPPED,
dwControlsAccepted: 0,
dwWin32ExitCode: 0,
dwServiceSpecificExitCode: 0,
dwCheckPoint: 0,
dwWaitHint: 0,
};
if unsafe {
winapi::um::winsvc::ControlService(
service.get_handle(),
winapi::um::winsvc::SERVICE_CONTROL_STOP,
&mut service_status,
)
} != 0
{
while unsafe {
winapi::um::winsvc::QueryServiceStatus(service.get_handle(), &mut service_status)
} != 0
{
if service_status.dwCurrentState != winapi::um::winsvc::SERVICE_STOP_PENDING {
break;
}
std::thread::sleep(std::time::Duration::from_millis(250));
}
}
Ok(())
}
pub fn start(&mut self) -> Result<(), StartServiceError> {
let service_manager = ServiceController::open(winapi::um::winsvc::SC_MANAGER_ALL_ACCESS)
.map_err(StartServiceError::WindowsError)?; let service = service_manager
.open_service(&self.name, winapi::um::winsvc::SERVICE_ALL_ACCESS)
.unwrap();
let mut service_status: winapi::um::winsvc::SERVICE_STATUS =
winapi::um::winsvc::SERVICE_STATUS {
dwServiceType: winapi::um::winnt::SERVICE_WIN32_OWN_PROCESS,
dwCurrentState: winapi::um::winsvc::SERVICE_STOPPED,
dwControlsAccepted: 0,
dwWin32ExitCode: 0,
dwServiceSpecificExitCode: 0,
dwCheckPoint: 0,
dwWaitHint: 0,
};
if unsafe { StartServiceW(service.handle, 0, std::ptr::null_mut()) } != 0 {
while unsafe { QueryServiceStatus(service.handle, &mut service_status) } != 0 {
if service_status.dwCurrentState != SERVICE_START_PENDING {
break;
}
std::thread::sleep(std::time::Duration::from_millis(250));
}
} else {
todo!();
}
if service_status.dwCurrentState != SERVICE_RUNNING {
println!("Failed to start service {}", service_status.dwCurrentState);
Err(StartServiceError::FailedToStart(
service_status.dwCurrentState,
))
} else {
Ok(())
}
}
pub fn delete(&mut self) -> Result<(), DWORD> {
let service_manager = ServiceController::open(winapi::um::winsvc::SC_MANAGER_ALL_ACCESS)?; let _e = eventlog::deregister(&format!("{} Log", self.name));
let service =
service_manager.open_service(&self.name, winapi::um::winsvc::SERVICE_ALL_ACCESS)?;
if unsafe { winapi::um::winsvc::DeleteService(service.get_handle()) } == 0 {
return Err(unsafe { winapi::um::errhandlingapi::GetLastError() });
}
Ok(())
}
#[cfg(feature = "async")]
pub async fn delete_async(&mut self) -> Result<(), DWORD> {
self.delete()
}
pub fn create(&mut self, config: ServiceConfig) -> Result<(), CreateServiceError> {
eventlog::register(&format!("{} Log", self.name)).unwrap();
let service_manager = ServiceController::open(winapi::um::winsvc::SC_MANAGER_ALL_ACCESS)
.map_err(CreateServiceError::WindowsError)?; let exe = config.binary.as_os_str().to_str().unwrap();
let args = config.arguments.join(" ");
let exe_with_args = if config.arguments.is_empty() {
exe.to_string()
} else {
format!("{} {}", exe, args)
};
let service = unsafe {
winapi::um::winsvc::CreateServiceW(
service_manager.get_handle(),
get_utf16(&self.name).as_ptr(),
get_utf16(&config.display).as_ptr(),
config.desired_access,
config.service_type,
config.start_type,
config.error_control,
get_utf16(&exe_with_args).as_ptr(),
get_optional_utf16(config.load_order_group.as_deref()),
std::ptr::null_mut(),
get_optional_utf16(config.dependencies.as_deref()),
get_optional_utf16(config.username.as_deref()),
get_optional_utf16(config.user_password.as_deref()),
)
};
if service.is_null() {
return Err(CreateServiceError::WindowsError(unsafe {
winapi::um::errhandlingapi::GetLastError()
}));
}
let mut description = get_utf16(&config.description);
let mut sd = winapi::um::winsvc::SERVICE_DESCRIPTIONW {
lpDescription: description.as_mut_ptr(),
};
let p_sd = &mut sd as *mut _ as *mut winapi::ctypes::c_void;
unsafe {
winapi::um::winsvc::ChangeServiceConfig2W(
service,
winapi::um::winsvc::SERVICE_CONFIG_DESCRIPTION,
p_sd,
)
};
unsafe { winapi::um::winsvc::CloseServiceHandle(service) };
Ok(())
}
#[cfg(feature = "async")]
pub async fn create_async(&mut self, config: ServiceConfig) -> Result<(), CreateServiceError> {
self.create(config)
}
pub fn dispatch(&self, service_main: DispatchFn) -> Result<(), DWORD> {
let service_name = get_utf16("");
let service_table: &[winapi::um::winsvc::SERVICE_TABLE_ENTRYW] = &[
winapi::um::winsvc::SERVICE_TABLE_ENTRYW {
lpServiceName: service_name.as_ptr() as _,
lpServiceProc: Some(service_main),
},
winapi::um::winsvc::SERVICE_TABLE_ENTRYW {
lpServiceName: std::ptr::null_mut(),
lpServiceProc: None,
},
];
let result =
unsafe { winapi::um::winsvc::StartServiceCtrlDispatcherW(service_table.as_ptr()) };
if result == 0 {
Err(unsafe { winapi::um::errhandlingapi::GetLastError() })
} else {
Ok(())
}
}
}
#[macro_export]
macro_rules! ServiceMacro {
($entry:ident, $function:ident, $t:ident) => {
extern "system" fn $entry(
argc: service::winapi::shared::minwindef::DWORD,
argv: *mut service::winapi::um::winnt::LPWSTR,
) {
let args = unsafe { service::convert_args(argc, argv) };
service::run_service::<$t>($function, args);
}
};
}
#[cfg(feature = "async")]
#[macro_export]
macro_rules! ServiceAsyncMacro {
($entry:ident, $function:ident, $t:ident) => {
extern "system" fn $entry(
argc: service::winapi::shared::minwindef::DWORD,
argv: *mut service::winapi::um::winnt::LPWSTR,
) {
let args = unsafe { service::convert_args(argc, argv) };
let name = args.get(0).unwrap();
let (tx, mut rx) = tokio::sync::mpsc::channel(10);
let tx2: tokio::sync::mpsc::Sender<service::ServiceEvent<$t>> = tx.clone();
let mut tx = Box::new(tx);
let handle = unsafe {
service::winapi::um::winsvc::RegisterServiceCtrlHandlerExW(
service::get_utf16(&name).as_ptr(),
Some(service::service_handler_async::<$t>),
Box::into_raw(tx) as service::winapi::shared::minwindef::LPVOID,
)
};
let mut sh = service::SERVICE_HANDLE.lock().unwrap();
*sh = service::ServiceStatusHandle::new(handle);
drop(sh);
unsafe {
service::set_service_status(
handle,
service::winapi::um::winsvc::SERVICE_START_PENDING,
0,
)
};
unsafe {
service::set_service_status(handle, service::winapi::um::winsvc::SERVICE_RUNNING, 0)
};
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
runtime.block_on(async move {
{
let main = tokio::task::spawn(smain());
loop {
tokio::select! {
Some(m) = rx.recv() => {
if let service::ServiceEvent::Stop = m {
break;
}
}
}
}
main.abort();
0
}
});
unsafe {
service::set_service_status(handle, service::winapi::um::winsvc::SERVICE_STOPPED, 0)
};
}
};
}
#[macro_export]
macro_rules! DispatchAsync {
($self:ident, $function:ident) => {{
$self.dispatch($function)
}};
}
#[tokio::main]
async fn do_the_thing<T>(tx: &mut tokio::sync::mpsc::Sender<crate::ServiceEvent<T>>) {
tx.send(crate::ServiceEvent::Stop).await;
}
fn do_service_handle<T>(
mut tx: Box<tokio::sync::mpsc::Sender<crate::ServiceEvent<T>>>,
control: DWORD,
event_type: winapi::shared::minwindef::DWORD,
session_id: Option<u32>,
) -> DWORD {
match control {
winapi::um::winsvc::SERVICE_CONTROL_STOP | winapi::um::winsvc::SERVICE_CONTROL_SHUTDOWN => {
use std::ops::DerefMut;
let mut sh = SERVICE_HANDLE.lock().unwrap();
let ServiceStatusHandle(h) = sh.deref_mut();
unsafe { set_service_status(*h, winapi::um::winsvc::SERVICE_STOP_PENDING, 10) };
drop(sh);
do_the_thing(&mut tx);
0
}
winapi::um::winsvc::SERVICE_CONTROL_SESSIONCHANGE => {
let event = event_type;
let session = Session(session_id.unwrap());
match event as usize {
winapi::um::winuser::WTS_CONSOLE_CONNECT => {
let _ = tx.blocking_send(crate::ServiceEvent::SessionConnect(session));
0
}
winapi::um::winuser::WTS_CONSOLE_DISCONNECT => {
let _ = tx.blocking_send(crate::ServiceEvent::SessionDisconnect(session));
0
}
winapi::um::winuser::WTS_REMOTE_CONNECT => {
let _ = tx.blocking_send(crate::ServiceEvent::SessionRemoteConnect(session));
0
}
winapi::um::winuser::WTS_REMOTE_DISCONNECT => {
let _ =
tx.blocking_send(crate::ServiceEvent::SessionRemoteDisconnect(session));
0
}
winapi::um::winuser::WTS_SESSION_LOGON => {
let _ = tx.blocking_send(crate::ServiceEvent::SessionLogon(session));
0
}
winapi::um::winuser::WTS_SESSION_LOGOFF => {
let _ = tx.blocking_send(crate::ServiceEvent::SessionLogoff(session));
0
}
winapi::um::winuser::WTS_SESSION_LOCK => {
let _ = tx.blocking_send(crate::ServiceEvent::SessionLock(session));
0
}
winapi::um::winuser::WTS_SESSION_UNLOCK => {
let _ = tx.blocking_send(crate::ServiceEvent::SessionUnlock(session));
0
}
_ => 0,
}
}
_ => winapi::shared::winerror::ERROR_CALL_NOT_IMPLEMENTED,
}
}
#[cfg(feature = "async")]
pub unsafe extern "system" fn service_handler_async<T: Send + 'static>(
control: winapi::shared::minwindef::DWORD,
event_type: winapi::shared::minwindef::DWORD,
event_data: winapi::shared::minwindef::LPVOID,
context: winapi::shared::minwindef::LPVOID,
) -> winapi::shared::minwindef::DWORD {
let tx = context as *mut tokio::sync::mpsc::Sender<crate::ServiceEvent<T>>;
let tx = Box::from_raw(tx);
let session_notification =
event_data as *const winapi::um::winuser::WTSSESSION_NOTIFICATION;
let session_id =
if let winapi::um::winsvc::SERVICE_CONTROL_SESSIONCHANGE = event_type {
Some(unsafe { (*session_notification).dwSessionId })
} else { None };
std::thread::spawn(move || {
do_service_handle(tx, control, event_type, session_id)
}).join().unwrap()
}
pub unsafe extern "system" fn service_handler<T>(
control: winapi::shared::minwindef::DWORD,
event_type: winapi::shared::minwindef::DWORD,
event_data: winapi::shared::minwindef::LPVOID,
context: winapi::shared::minwindef::LPVOID,
) -> winapi::shared::minwindef::DWORD {
let tx = context as *mut std::sync::mpsc::Sender<crate::ServiceEvent<T>>;
match control {
winapi::um::winsvc::SERVICE_CONTROL_STOP | winapi::um::winsvc::SERVICE_CONTROL_SHUTDOWN => {
use std::ops::DerefMut;
let mut sh = SERVICE_HANDLE.lock().unwrap();
let ServiceStatusHandle(h) = sh.deref_mut();
set_service_status(*h, winapi::um::winsvc::SERVICE_STOP_PENDING, 10);
drop(sh);
let _ = (*tx).send(crate::ServiceEvent::Stop);
0
}
winapi::um::winsvc::SERVICE_CONTROL_PAUSE => {
let _ = (*tx).send(crate::ServiceEvent::Pause);
0
}
winapi::um::winsvc::SERVICE_CONTROL_CONTINUE => {
let _ = (*tx).send(crate::ServiceEvent::Continue);
0
}
winapi::um::winsvc::SERVICE_CONTROL_SESSIONCHANGE => {
let event = event_type;
let session_notification =
event_data as *const winapi::um::winuser::WTSSESSION_NOTIFICATION;
let session_id = (*session_notification).dwSessionId;
let session = Session(session_id);
match event as usize {
winapi::um::winuser::WTS_CONSOLE_CONNECT => {
let _ = (*tx).send(crate::ServiceEvent::SessionConnect(session));
0
}
winapi::um::winuser::WTS_CONSOLE_DISCONNECT => {
let _ = (*tx).send(crate::ServiceEvent::SessionDisconnect(session));
0
}
winapi::um::winuser::WTS_REMOTE_CONNECT => {
let _ = (*tx).send(crate::ServiceEvent::SessionRemoteConnect(session));
0
}
winapi::um::winuser::WTS_REMOTE_DISCONNECT => {
let _ = (*tx).send(crate::ServiceEvent::SessionRemoteDisconnect(session));
0
}
winapi::um::winuser::WTS_SESSION_LOGON => {
let _ = (*tx).send(crate::ServiceEvent::SessionLogon(session));
0
}
winapi::um::winuser::WTS_SESSION_LOGOFF => {
let _ = (*tx).send(crate::ServiceEvent::SessionLogoff(session));
0
}
winapi::um::winuser::WTS_SESSION_LOCK => {
let _ = (*tx).send(crate::ServiceEvent::SessionLock(session));
0
}
winapi::um::winuser::WTS_SESSION_UNLOCK => {
let _ = (*tx).send(crate::ServiceEvent::SessionUnlock(session));
0
}
_ => 0,
}
}
_ => winapi::shared::winerror::ERROR_CALL_NOT_IMPLEMENTED,
}
}
pub unsafe fn set_service_status(
status_handle: winapi::um::winsvc::SERVICE_STATUS_HANDLE,
current_state: winapi::shared::minwindef::DWORD,
wait_hint: winapi::shared::minwindef::DWORD,
) {
let mut service_status = winapi::um::winsvc::SERVICE_STATUS {
dwServiceType: winapi::um::winnt::SERVICE_WIN32_OWN_PROCESS,
dwCurrentState: current_state,
dwControlsAccepted: winapi::um::winsvc::SERVICE_ACCEPT_STOP
| winapi::um::winsvc::SERVICE_ACCEPT_SHUTDOWN,
dwWin32ExitCode: 0,
dwServiceSpecificExitCode: 0,
dwCheckPoint: 0,
dwWaitHint: wait_hint,
};
winapi::um::winsvc::SetServiceStatus(status_handle, &mut service_status);
}
pub fn run_service<T: std::marker::Send + 'static>(service_main: ServiceFn<T>, args: Vec<String>) {
let name = args.first().unwrap();
let (tx, rx) = std::sync::mpsc::channel();
let tx2: std::sync::mpsc::Sender<crate::ServiceEvent<T>> = tx.clone();
let tx = Box::new(tx);
let handle = unsafe {
winapi::um::winsvc::RegisterServiceCtrlHandlerExW(
get_utf16(name).as_ptr(),
Some(service_handler::<T>),
Box::into_raw(tx) as winapi::shared::minwindef::LPVOID,
)
};
let mut sh = SERVICE_HANDLE.lock().unwrap();
*sh = ServiceStatusHandle(handle);
drop(sh);
unsafe { set_service_status(handle, winapi::um::winsvc::SERVICE_START_PENDING, 0) };
unsafe { set_service_status(handle, winapi::um::winsvc::SERVICE_RUNNING, 0) };
let service_thread = std::thread::spawn(move || {
service_main(Some(rx), Some(tx2));
});
let _e = service_thread.join();
unsafe { set_service_status(handle, winapi::um::winsvc::SERVICE_STOPPED, 0) };
}