use std::borrow::Cow;
use std::ffi::{OsStr, OsString};
use std::os::raw::c_void;
use std::os::windows::ffi::{OsStrExt, OsStringExt};
use std::path::PathBuf;
use std::ptr;
use std::time::Duration;
use std::{io, mem};
use widestring::{error::ContainsNul, WideCStr, WideCString, WideString};
use windows_sys::{
core::GUID,
Win32::{
Foundation::{ERROR_SERVICE_SPECIFIC_ERROR, NO_ERROR},
Storage::FileSystem,
System::{Power, RemoteDesktop, Services, SystemServices, Threading::INFINITE},
UI::WindowsAndMessaging,
},
};
use crate::sc_handle::ScHandle;
use crate::shell_escape;
use crate::{double_nul_terminated, Error};
bitflags::bitflags! {
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
pub struct ServiceType: u32 {
const FILE_SYSTEM_DRIVER = Services::SERVICE_FILE_SYSTEM_DRIVER;
const KERNEL_DRIVER = Services::SERVICE_KERNEL_DRIVER;
const OWN_PROCESS = Services::SERVICE_WIN32_OWN_PROCESS;
const SHARE_PROCESS = Services::SERVICE_WIN32_SHARE_PROCESS;
const USER_OWN_PROCESS = Services::SERVICE_USER_OWN_PROCESS;
const USER_SHARE_PROCESS = Services::SERVICE_USER_SHARE_PROCESS;
const INTERACTIVE_PROCESS = SystemServices::SERVICE_INTERACTIVE_PROCESS;
}
}
bitflags::bitflags! {
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
pub struct ServiceAccess: u32 {
const ALL_ACCESS = Services::SERVICE_ALL_ACCESS;
const QUERY_STATUS = Services::SERVICE_QUERY_STATUS;
const START = Services::SERVICE_START;
const STOP = Services::SERVICE_STOP;
const PAUSE_CONTINUE = Services::SERVICE_PAUSE_CONTINUE;
const INTERROGATE = Services::SERVICE_INTERROGATE;
const QUERY_CONFIG = Services::SERVICE_QUERY_CONFIG;
const CHANGE_CONFIG = Services::SERVICE_CHANGE_CONFIG;
const USER_DEFINED_CONTROL = Services::SERVICE_USER_DEFINED_CONTROL;
const DELETE = FileSystem::DELETE;
const READ_CONTROL = FileSystem::READ_CONTROL;
const WRITE_DAC = FileSystem::WRITE_DAC;
const WRITE_OWNER = FileSystem::WRITE_OWNER;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum ServiceStartType {
AutoStart = Services::SERVICE_AUTO_START,
OnDemand = Services::SERVICE_DEMAND_START,
Disabled = Services::SERVICE_DISABLED,
SystemStart = Services::SERVICE_SYSTEM_START,
BootStart = Services::SERVICE_BOOT_START,
}
impl ServiceStartType {
pub fn to_raw(&self) -> u32 {
*self as u32
}
pub fn from_raw(raw: u32) -> Result<ServiceStartType, ParseRawError> {
match raw {
x if x == ServiceStartType::AutoStart.to_raw() => Ok(ServiceStartType::AutoStart),
x if x == ServiceStartType::OnDemand.to_raw() => Ok(ServiceStartType::OnDemand),
x if x == ServiceStartType::Disabled.to_raw() => Ok(ServiceStartType::Disabled),
x if x == ServiceStartType::SystemStart.to_raw() => Ok(ServiceStartType::SystemStart),
x if x == ServiceStartType::BootStart.to_raw() => Ok(ServiceStartType::BootStart),
_ => Err(ParseRawError::InvalidInteger(raw)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum ServiceErrorControl {
Critical = Services::SERVICE_ERROR_CRITICAL,
Ignore = Services::SERVICE_ERROR_IGNORE,
Normal = Services::SERVICE_ERROR_NORMAL,
Severe = Services::SERVICE_ERROR_SEVERE,
}
impl ServiceErrorControl {
pub fn to_raw(&self) -> u32 {
*self as u32
}
pub fn from_raw(raw: u32) -> Result<ServiceErrorControl, ParseRawError> {
match raw {
x if x == ServiceErrorControl::Critical.to_raw() => Ok(ServiceErrorControl::Critical),
x if x == ServiceErrorControl::Ignore.to_raw() => Ok(ServiceErrorControl::Ignore),
x if x == ServiceErrorControl::Normal.to_raw() => Ok(ServiceErrorControl::Normal),
x if x == ServiceErrorControl::Severe.to_raw() => Ok(ServiceErrorControl::Severe),
_ => Err(ParseRawError::InvalidInteger(raw)),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ServiceDependency {
Service(OsString),
Group(OsString),
}
impl ServiceDependency {
pub fn to_system_identifier(&self) -> OsString {
match *self {
ServiceDependency::Service(ref name) => name.to_owned(),
ServiceDependency::Group(ref name) => {
let mut group_identifier = OsString::new();
group_identifier.push("+");
group_identifier.push(name);
group_identifier
}
}
}
pub fn from_system_identifier(identifier: impl AsRef<OsStr>) -> Self {
let group_prefix: u16 = '+' as u16;
let mut iter = identifier.as_ref().encode_wide().peekable();
if iter.peek() == Some(&group_prefix) {
let chars: Vec<u16> = iter.skip(1).collect();
let group_name = OsString::from_wide(&chars);
ServiceDependency::Group(group_name)
} else {
let chars: Vec<u16> = iter.collect();
let service_name = OsString::from_wide(&chars);
ServiceDependency::Service(service_name)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(i32)]
pub enum ServiceActionType {
None = Services::SC_ACTION_NONE,
Reboot = Services::SC_ACTION_REBOOT,
Restart = Services::SC_ACTION_RESTART,
RunCommand = Services::SC_ACTION_RUN_COMMAND,
}
impl ServiceActionType {
pub fn to_raw(&self) -> i32 {
*self as i32
}
pub fn from_raw(raw: i32) -> Result<ServiceActionType, ParseRawError> {
match raw {
x if x == ServiceActionType::None.to_raw() => Ok(ServiceActionType::None),
x if x == ServiceActionType::Reboot.to_raw() => Ok(ServiceActionType::Reboot),
x if x == ServiceActionType::Restart.to_raw() => Ok(ServiceActionType::Restart),
x if x == ServiceActionType::RunCommand.to_raw() => Ok(ServiceActionType::RunCommand),
_ => Err(ParseRawError::InvalidIntegerSigned(raw)),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ServiceAction {
pub action_type: ServiceActionType,
pub delay: Duration,
}
impl ServiceAction {
pub fn from_raw(raw: Services::SC_ACTION) -> crate::Result<ServiceAction> {
Ok(ServiceAction {
action_type: ServiceActionType::from_raw(raw.Type)
.map_err(|e| Error::ParseValue("service action type", e))?,
delay: Duration::from_millis(raw.Delay as u64),
})
}
pub fn to_raw(&self) -> Services::SC_ACTION {
Services::SC_ACTION {
Type: self.action_type.to_raw(),
Delay: u32::try_from(self.delay.as_millis()).expect("Too long delay"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ServiceFailureResetPeriod {
Never,
After(Duration),
}
impl ServiceFailureResetPeriod {
pub fn from_raw(raw: u32) -> ServiceFailureResetPeriod {
match raw {
INFINITE => ServiceFailureResetPeriod::Never,
_ => ServiceFailureResetPeriod::After(Duration::from_secs(raw as u64)),
}
}
pub fn to_raw(&self) -> u32 {
match self {
ServiceFailureResetPeriod::Never => INFINITE,
ServiceFailureResetPeriod::After(duration) => {
u32::try_from(duration.as_secs()).expect("Too long reset period")
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ServiceFailureActions {
pub reset_period: ServiceFailureResetPeriod,
pub reboot_msg: Option<OsString>,
pub command: Option<OsString>,
pub actions: Option<Vec<ServiceAction>>,
}
impl ServiceFailureActions {
pub unsafe fn from_raw(
raw: Services::SERVICE_FAILURE_ACTIONSW,
) -> crate::Result<ServiceFailureActions> {
let reboot_msg = ptr::NonNull::new(raw.lpRebootMsg)
.map(|wrapped_ptr| WideCStr::from_ptr_str(wrapped_ptr.as_ptr()).to_os_string());
let command = ptr::NonNull::new(raw.lpCommand)
.map(|wrapped_ptr| WideCStr::from_ptr_str(wrapped_ptr.as_ptr()).to_os_string());
let reset_period = ServiceFailureResetPeriod::from_raw(raw.dwResetPeriod);
let actions: Option<Vec<ServiceAction>> = if raw.lpsaActions.is_null() {
None
} else {
Some(
(0..raw.cActions)
.map(|i| {
let array_element_ptr: *mut Services::SC_ACTION =
raw.lpsaActions.offset(i as isize);
ServiceAction::from_raw(*array_element_ptr)
})
.collect::<crate::Result<Vec<ServiceAction>>>()?,
)
};
Ok(ServiceFailureActions {
reset_period,
reboot_msg,
command,
actions,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ServiceInfo {
pub name: OsString,
pub display_name: OsString,
pub service_type: ServiceType,
pub start_type: ServiceStartType,
pub error_control: ServiceErrorControl,
pub executable_path: PathBuf,
pub launch_arguments: Vec<OsString>,
pub dependencies: Vec<ServiceDependency>,
pub account_name: Option<OsString>,
pub account_password: Option<OsString>,
}
pub(crate) struct RawServiceInfo {
pub name: WideCString,
pub display_name: WideCString,
pub service_type: u32,
pub start_type: u32,
pub error_control: u32,
pub launch_command: WideCString,
pub dependencies: Option<WideString>,
pub account_name: Option<WideCString>,
pub account_password: Option<WideCString>,
}
impl RawServiceInfo {
pub fn new(service_info: &ServiceInfo) -> crate::Result<Self> {
let service_name = WideCString::from_os_str(&service_info.name)
.map_err(|_| Error::ArgumentHasNulByte("service name"))?;
let display_name = WideCString::from_os_str(&service_info.display_name)
.map_err(|_| Error::ArgumentHasNulByte("display name"))?;
let account_name = to_wide(service_info.account_name.as_ref())
.map_err(|_| Error::ArgumentHasNulByte("account name"))?;
let account_password = to_wide(service_info.account_password.as_ref())
.map_err(|_| Error::ArgumentHasNulByte("account password"))?;
let mut launch_command_buffer = WideString::new();
if service_info
.service_type
.intersects(ServiceType::KERNEL_DRIVER | ServiceType::FILE_SYSTEM_DRIVER)
{
if !service_info.launch_arguments.is_empty() {
return Err(Error::LaunchArgumentsNotSupported);
}
let executable_path = WideCString::from_os_str(&service_info.executable_path)
.map_err(|_| Error::ArgumentHasNulByte("executable path"))?;
launch_command_buffer.push(executable_path.to_ustring());
} else {
let executable_path = escape_wide(&service_info.executable_path)
.map_err(|_| Error::ArgumentHasNulByte("executable path"))?;
launch_command_buffer.push(executable_path);
for (i, launch_argument) in service_info.launch_arguments.iter().enumerate() {
let wide = escape_wide(launch_argument)
.map_err(|_| Error::ArgumentArrayElementHasNulByte("launch argument", i))?;
launch_command_buffer.push_str(" ");
launch_command_buffer.push(wide);
}
}
let launch_command = unsafe { WideCString::from_ustr_unchecked(launch_command_buffer) };
let dependency_identifiers: Vec<OsString> = service_info
.dependencies
.iter()
.map(|dependency| dependency.to_system_identifier())
.collect();
let joined_dependencies = double_nul_terminated::from_slice(&dependency_identifiers)
.map_err(|_| Error::ArgumentHasNulByte("dependency"))?;
Ok(Self {
name: service_name,
display_name,
service_type: service_info.service_type.bits(),
start_type: service_info.start_type.to_raw(),
error_control: service_info.error_control.to_raw(),
launch_command,
dependencies: joined_dependencies,
account_name,
account_password,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ServiceConfig {
pub service_type: ServiceType,
pub start_type: ServiceStartType,
pub error_control: ServiceErrorControl,
pub executable_path: PathBuf,
pub load_order_group: Option<OsString>,
pub tag_id: u32,
pub dependencies: Vec<ServiceDependency>,
pub account_name: Option<OsString>,
pub display_name: OsString,
}
impl ServiceConfig {
pub unsafe fn from_raw(raw: Services::QUERY_SERVICE_CONFIGW) -> crate::Result<ServiceConfig> {
let dependencies = double_nul_terminated::parse_str_ptr(raw.lpDependencies)
.iter()
.map(ServiceDependency::from_system_identifier)
.collect();
let load_order_group = ptr::NonNull::new(raw.lpLoadOrderGroup).and_then(|wrapped_ptr| {
let group = WideCStr::from_ptr_str(wrapped_ptr.as_ptr()).to_os_string();
if group.is_empty() {
None
} else {
Some(group)
}
});
let account_name = ptr::NonNull::new(raw.lpServiceStartName)
.map(|wrapped_ptr| WideCStr::from_ptr_str(wrapped_ptr.as_ptr()).to_os_string());
Ok(ServiceConfig {
service_type: ServiceType::from_bits_truncate(raw.dwServiceType),
start_type: ServiceStartType::from_raw(raw.dwStartType)
.map_err(|e| Error::ParseValue("service start type", e))?,
error_control: ServiceErrorControl::from_raw(raw.dwErrorControl)
.map_err(|e| Error::ParseValue("service error control", e))?,
executable_path: PathBuf::from(
WideCStr::from_ptr_str(raw.lpBinaryPathName).to_os_string(),
),
load_order_group,
tag_id: raw.dwTagId,
dependencies,
account_name,
display_name: WideCStr::from_ptr_str(raw.lpDisplayName).to_os_string(),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum HardwareProfileChangeParam {
ConfigChanged = WindowsAndMessaging::DBT_CONFIGCHANGED,
QueryChangeConfig = WindowsAndMessaging::DBT_QUERYCHANGECONFIG,
ConfigChangeCanceled = WindowsAndMessaging::DBT_CONFIGCHANGECANCELED,
}
impl HardwareProfileChangeParam {
pub fn to_raw(&self) -> u32 {
*self as u32
}
pub fn from_raw(raw: u32) -> Result<Self, ParseRawError> {
match raw {
x if x == HardwareProfileChangeParam::ConfigChanged.to_raw() => {
Ok(HardwareProfileChangeParam::ConfigChanged)
}
x if x == HardwareProfileChangeParam::QueryChangeConfig.to_raw() => {
Ok(HardwareProfileChangeParam::QueryChangeConfig)
}
x if x == HardwareProfileChangeParam::ConfigChangeCanceled.to_raw() => {
Ok(HardwareProfileChangeParam::ConfigChangeCanceled)
}
_ => Err(ParseRawError::InvalidInteger(raw)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(i32)]
pub enum PowerSource {
Ac = Power::PoAc,
Dc = Power::PoDc,
Hot = Power::PoHot,
}
impl PowerSource {
pub fn to_raw(&self) -> i32 {
*self as i32
}
pub fn from_raw(raw: i32) -> Result<PowerSource, ParseRawError> {
match raw {
x if x == PowerSource::Ac.to_raw() => Ok(PowerSource::Ac),
x if x == PowerSource::Dc.to_raw() => Ok(PowerSource::Dc),
x if x == PowerSource::Hot.to_raw() => Ok(PowerSource::Hot),
_ => Err(ParseRawError::InvalidIntegerSigned(raw)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(i32)]
pub enum DisplayState {
Off = SystemServices::PowerMonitorOff,
On = SystemServices::PowerMonitorOn,
Dimmed = SystemServices::PowerMonitorDim,
}
impl DisplayState {
pub fn to_raw(&self) -> i32 {
*self as i32
}
pub fn from_raw(raw: i32) -> Result<DisplayState, ParseRawError> {
match raw {
x if x == DisplayState::Off.to_raw() => Ok(DisplayState::Off),
x if x == DisplayState::On.to_raw() => Ok(DisplayState::On),
x if x == DisplayState::Dimmed.to_raw() => Ok(DisplayState::Dimmed),
_ => Err(ParseRawError::InvalidIntegerSigned(raw)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(i32)]
pub enum UserStatus {
Present = Power::PowerUserPresent,
Inactive = Power::PowerUserInactive,
}
impl UserStatus {
pub fn to_raw(&self) -> i32 {
*self as i32
}
pub fn from_raw(raw: i32) -> Result<UserStatus, ParseRawError> {
match raw {
x if x == UserStatus::Present.to_raw() => Ok(UserStatus::Present),
x if x == UserStatus::Inactive.to_raw() => Ok(UserStatus::Inactive),
_ => Err(ParseRawError::InvalidIntegerSigned(raw)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum MonitorState {
Off = 0,
On = 1,
}
impl MonitorState {
pub fn to_raw(&self) -> u32 {
*self as u32
}
pub fn from_raw(raw: u32) -> Result<MonitorState, ParseRawError> {
match raw {
x if x == MonitorState::Off.to_raw() => Ok(MonitorState::Off),
x if x == MonitorState::On.to_raw() => Ok(MonitorState::On),
_ => Err(ParseRawError::InvalidInteger(raw)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum BatterySaverState {
Off = 0,
On = 1,
}
impl BatterySaverState {
pub fn to_raw(&self) -> u32 {
*self as u32
}
pub fn from_raw(raw: u32) -> Result<BatterySaverState, ParseRawError> {
match raw {
x if x == BatterySaverState::Off.to_raw() => Ok(BatterySaverState::Off),
x if x == BatterySaverState::On.to_raw() => Ok(BatterySaverState::On),
_ => Err(ParseRawError::InvalidInteger(raw)),
}
}
}
fn is_equal_guid(a: &GUID, b: &GUID) -> bool {
a.data1 == b.data1 && a.data2 == b.data2 && a.data3 == b.data3 && a.data4 == b.data4
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PowerSchemePersonality {
HighPerformance,
PowerSaver,
Automatic,
}
impl PowerSchemePersonality {
pub fn from_guid(guid: &GUID) -> Result<PowerSchemePersonality, ParseRawError> {
match guid {
x if is_equal_guid(x, &SystemServices::GUID_MIN_POWER_SAVINGS) => {
Ok(PowerSchemePersonality::HighPerformance)
}
x if is_equal_guid(x, &SystemServices::GUID_MAX_POWER_SAVINGS) => {
Ok(PowerSchemePersonality::PowerSaver)
}
x if is_equal_guid(x, &SystemServices::GUID_TYPICAL_POWER_SAVINGS) => {
Ok(PowerSchemePersonality::Automatic)
}
x => Err(ParseRawError::InvalidGuid(string_from_guid(x))),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum AwayModeState {
Exiting = 0,
Entering = 1,
}
impl AwayModeState {
pub fn to_raw(&self) -> u32 {
*self as u32
}
pub fn from_raw(raw: u32) -> Result<AwayModeState, ParseRawError> {
match raw {
x if x == AwayModeState::Exiting.to_raw() => Ok(AwayModeState::Exiting),
x if x == AwayModeState::Entering.to_raw() => Ok(AwayModeState::Entering),
_ => Err(ParseRawError::InvalidInteger(raw)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum LidSwitchStateChange {
Closed = 0,
Open = 1,
}
impl LidSwitchStateChange {
pub fn to_raw(&self) -> u32 {
*self as u32
}
pub fn from_raw(raw: u32) -> Result<LidSwitchStateChange, ParseRawError> {
match raw {
x if x == LidSwitchStateChange::Closed.to_raw() => Ok(LidSwitchStateChange::Closed),
x if x == LidSwitchStateChange::Open.to_raw() => Ok(LidSwitchStateChange::Open),
_ => Err(ParseRawError::InvalidInteger(raw)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum PowerBroadcastSetting {
AcdcPowerSource(PowerSource),
BatteryPercentageRemaining(u32),
ConsoleDisplayState(DisplayState),
GlobalUserPresence(UserStatus),
IdleBackgroundTask,
MonitorPowerOn(MonitorState),
PowerSavingStatus(BatterySaverState),
PowerSchemePersonality(PowerSchemePersonality),
SystemAwayMode(AwayModeState),
LidSwitchStateChange(LidSwitchStateChange),
}
impl PowerBroadcastSetting {
pub unsafe fn from_raw(raw: *mut c_void) -> Result<PowerBroadcastSetting, ParseRawError> {
let setting = &*(raw as *const Power::POWERBROADCAST_SETTING);
let data = &setting.Data as *const u8;
match &setting.PowerSetting {
x if is_equal_guid(x, &SystemServices::GUID_ACDC_POWER_SOURCE) => {
let power_source = *(data as *const i32);
Ok(PowerBroadcastSetting::AcdcPowerSource(
PowerSource::from_raw(power_source)?,
))
}
x if is_equal_guid(x, &SystemServices::GUID_BATTERY_PERCENTAGE_REMAINING) => {
let percentage = *(data as *const u32);
Ok(PowerBroadcastSetting::BatteryPercentageRemaining(
percentage,
))
}
x if is_equal_guid(x, &SystemServices::GUID_CONSOLE_DISPLAY_STATE) => {
let display_state = *(data as *const i32);
Ok(PowerBroadcastSetting::ConsoleDisplayState(
DisplayState::from_raw(display_state)?,
))
}
x if is_equal_guid(x, &SystemServices::GUID_GLOBAL_USER_PRESENCE) => {
let user_status = *(data as *const i32);
Ok(PowerBroadcastSetting::GlobalUserPresence(
UserStatus::from_raw(user_status)?,
))
}
x if is_equal_guid(x, &SystemServices::GUID_IDLE_BACKGROUND_TASK) => {
Ok(PowerBroadcastSetting::IdleBackgroundTask)
}
x if is_equal_guid(x, &SystemServices::GUID_MONITOR_POWER_ON) => {
let monitor_state = *(data as *const u32);
Ok(PowerBroadcastSetting::MonitorPowerOn(
MonitorState::from_raw(monitor_state)?,
))
}
x if is_equal_guid(x, &SystemServices::GUID_POWER_SAVING_STATUS) => {
let battery_saver_state = *(data as *const u32);
Ok(PowerBroadcastSetting::PowerSavingStatus(
BatterySaverState::from_raw(battery_saver_state)?,
))
}
x if is_equal_guid(x, &SystemServices::GUID_POWERSCHEME_PERSONALITY) => {
let guid = *(data as *const GUID);
Ok(PowerBroadcastSetting::PowerSchemePersonality(
PowerSchemePersonality::from_guid(&guid)?,
))
}
x if is_equal_guid(x, &SystemServices::GUID_SYSTEM_AWAYMODE) => {
let away_mode_state = *(data as *const u32);
Ok(PowerBroadcastSetting::SystemAwayMode(
AwayModeState::from_raw(away_mode_state)?,
))
}
x if is_equal_guid(x, &SystemServices::GUID_LIDSWITCH_STATE_CHANGE) => {
let lid_switch_state = *(data as *const u32);
Ok(PowerBroadcastSetting::LidSwitchStateChange(
LidSwitchStateChange::from_raw(lid_switch_state)?,
))
}
x => Err(ParseRawError::InvalidGuid(string_from_guid(x))),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum PowerEventParam {
PowerStatusChange,
ResumeAutomatic,
ResumeSuspend,
Suspend,
PowerSettingChange(PowerBroadcastSetting),
BatteryLow,
OemEvent,
QuerySuspend,
QuerySuspendFailed,
ResumeCritical,
}
impl PowerEventParam {
pub unsafe fn from_event(
event_type: u32,
event_data: *mut c_void,
) -> Result<Self, ParseRawError> {
match event_type {
WindowsAndMessaging::PBT_APMPOWERSTATUSCHANGE => Ok(PowerEventParam::PowerStatusChange),
WindowsAndMessaging::PBT_APMRESUMEAUTOMATIC => Ok(PowerEventParam::ResumeAutomatic),
WindowsAndMessaging::PBT_APMRESUMESUSPEND => Ok(PowerEventParam::ResumeSuspend),
WindowsAndMessaging::PBT_APMSUSPEND => Ok(PowerEventParam::Suspend),
WindowsAndMessaging::PBT_POWERSETTINGCHANGE => Ok(PowerEventParam::PowerSettingChange(
PowerBroadcastSetting::from_raw(event_data)?,
)),
WindowsAndMessaging::PBT_APMBATTERYLOW => Ok(PowerEventParam::BatteryLow),
WindowsAndMessaging::PBT_APMOEMEVENT => Ok(PowerEventParam::OemEvent),
WindowsAndMessaging::PBT_APMQUERYSUSPEND => Ok(PowerEventParam::QuerySuspend),
WindowsAndMessaging::PBT_APMQUERYSUSPENDFAILED => {
Ok(PowerEventParam::QuerySuspendFailed)
}
WindowsAndMessaging::PBT_APMRESUMECRITICAL => Ok(PowerEventParam::ResumeCritical),
_ => Err(ParseRawError::InvalidInteger(event_type)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum SessionChangeReason {
ConsoleConnect = WindowsAndMessaging::WTS_CONSOLE_CONNECT,
ConsoleDisconnect = WindowsAndMessaging::WTS_CONSOLE_DISCONNECT,
RemoteConnect = WindowsAndMessaging::WTS_REMOTE_CONNECT,
RemoteDisconnect = WindowsAndMessaging::WTS_REMOTE_DISCONNECT,
SessionLogon = WindowsAndMessaging::WTS_SESSION_LOGON,
SessionLogoff = WindowsAndMessaging::WTS_SESSION_LOGOFF,
SessionLock = WindowsAndMessaging::WTS_SESSION_LOCK,
SessionUnlock = WindowsAndMessaging::WTS_SESSION_UNLOCK,
SessionRemoteControl = WindowsAndMessaging::WTS_SESSION_REMOTE_CONTROL,
SessionCreate = WindowsAndMessaging::WTS_SESSION_CREATE,
SessionTerminate = WindowsAndMessaging::WTS_SESSION_TERMINATE,
}
impl SessionChangeReason {
pub fn from_raw(raw: u32) -> Result<SessionChangeReason, ParseRawError> {
match raw {
x if x == SessionChangeReason::ConsoleConnect.to_raw() => {
Ok(SessionChangeReason::ConsoleConnect)
}
x if x == SessionChangeReason::ConsoleDisconnect.to_raw() => {
Ok(SessionChangeReason::ConsoleDisconnect)
}
x if x == SessionChangeReason::RemoteConnect.to_raw() => {
Ok(SessionChangeReason::RemoteConnect)
}
x if x == SessionChangeReason::RemoteDisconnect.to_raw() => {
Ok(SessionChangeReason::RemoteDisconnect)
}
x if x == SessionChangeReason::SessionLogon.to_raw() => {
Ok(SessionChangeReason::SessionLogon)
}
x if x == SessionChangeReason::SessionLogoff.to_raw() => {
Ok(SessionChangeReason::SessionLogoff)
}
x if x == SessionChangeReason::SessionLock.to_raw() => {
Ok(SessionChangeReason::SessionLock)
}
x if x == SessionChangeReason::SessionUnlock.to_raw() => {
Ok(SessionChangeReason::SessionUnlock)
}
x if x == SessionChangeReason::SessionRemoteControl.to_raw() => {
Ok(SessionChangeReason::SessionRemoteControl)
}
x if x == SessionChangeReason::SessionCreate.to_raw() => {
Ok(SessionChangeReason::SessionCreate)
}
x if x == SessionChangeReason::SessionTerminate.to_raw() => {
Ok(SessionChangeReason::SessionTerminate)
}
_ => Err(ParseRawError::InvalidInteger(raw)),
}
}
pub fn to_raw(&self) -> u32 {
*self as u32
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SessionNotification {
pub size: u32,
pub session_id: u32,
}
impl SessionNotification {
pub fn from_raw(raw: RemoteDesktop::WTSSESSION_NOTIFICATION) -> Self {
SessionNotification {
size: raw.cbSize,
session_id: raw.dwSessionId,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SessionChangeParam {
pub reason: SessionChangeReason,
pub notification: SessionNotification,
}
impl SessionChangeParam {
pub unsafe fn from_event(
event_type: u32,
event_data: *mut c_void,
) -> Result<Self, ParseRawError> {
let notification = *(event_data as *const RemoteDesktop::WTSSESSION_NOTIFICATION);
Ok(SessionChangeParam {
reason: SessionChangeReason::from_raw(event_type)?,
notification: SessionNotification::from_raw(notification),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct UserEventCode(u32);
impl UserEventCode {
pub const unsafe fn from_unchecked(raw: u32) -> Self {
Self(raw)
}
pub fn from_raw(raw: u32) -> Result<UserEventCode, ParseRawError> {
match raw {
128..=255 => Ok(Self(raw)),
_ => Err(ParseRawError::InvalidInteger(raw)),
}
}
pub fn to_raw(&self) -> u32 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ServiceControl {
Continue,
Interrogate,
NetBindAdd,
NetBindDisable,
NetBindEnable,
NetBindRemove,
ParamChange,
Pause,
Preshutdown,
Shutdown,
Stop,
HardwareProfileChange(HardwareProfileChangeParam),
PowerEvent(PowerEventParam),
SessionChange(SessionChangeParam),
TimeChange,
TriggerEvent,
UserEvent(UserEventCode),
}
impl ServiceControl {
pub unsafe fn from_raw(
raw: u32,
event_type: u32,
event_data: *mut c_void,
) -> Result<Self, ParseRawError> {
match raw {
Services::SERVICE_CONTROL_CONTINUE => Ok(ServiceControl::Continue),
Services::SERVICE_CONTROL_INTERROGATE => Ok(ServiceControl::Interrogate),
Services::SERVICE_CONTROL_NETBINDADD => Ok(ServiceControl::NetBindAdd),
Services::SERVICE_CONTROL_NETBINDDISABLE => Ok(ServiceControl::NetBindDisable),
Services::SERVICE_CONTROL_NETBINDENABLE => Ok(ServiceControl::NetBindEnable),
Services::SERVICE_CONTROL_NETBINDREMOVE => Ok(ServiceControl::NetBindRemove),
Services::SERVICE_CONTROL_PARAMCHANGE => Ok(ServiceControl::ParamChange),
Services::SERVICE_CONTROL_PAUSE => Ok(ServiceControl::Pause),
Services::SERVICE_CONTROL_PRESHUTDOWN => Ok(ServiceControl::Preshutdown),
Services::SERVICE_CONTROL_SHUTDOWN => Ok(ServiceControl::Shutdown),
Services::SERVICE_CONTROL_STOP => Ok(ServiceControl::Stop),
Services::SERVICE_CONTROL_HARDWAREPROFILECHANGE => {
HardwareProfileChangeParam::from_raw(event_type)
.map(ServiceControl::HardwareProfileChange)
}
Services::SERVICE_CONTROL_POWEREVENT => {
PowerEventParam::from_event(event_type, event_data).map(ServiceControl::PowerEvent)
}
Services::SERVICE_CONTROL_SESSIONCHANGE => {
SessionChangeParam::from_event(event_type, event_data)
.map(ServiceControl::SessionChange)
}
Services::SERVICE_CONTROL_TIMECHANGE => Ok(ServiceControl::TimeChange),
Services::SERVICE_CONTROL_TRIGGEREVENT => Ok(ServiceControl::TriggerEvent),
_ => UserEventCode::from_raw(raw).map(ServiceControl::UserEvent),
}
}
pub fn raw_service_control_type(&self) -> u32 {
match self {
ServiceControl::Continue => Services::SERVICE_CONTROL_CONTINUE,
ServiceControl::Interrogate => Services::SERVICE_CONTROL_INTERROGATE,
ServiceControl::NetBindAdd => Services::SERVICE_CONTROL_NETBINDADD,
ServiceControl::NetBindDisable => Services::SERVICE_CONTROL_NETBINDDISABLE,
ServiceControl::NetBindEnable => Services::SERVICE_CONTROL_NETBINDENABLE,
ServiceControl::NetBindRemove => Services::SERVICE_CONTROL_NETBINDREMOVE,
ServiceControl::ParamChange => Services::SERVICE_CONTROL_PARAMCHANGE,
ServiceControl::Pause => Services::SERVICE_CONTROL_PAUSE,
ServiceControl::Preshutdown => Services::SERVICE_CONTROL_PRESHUTDOWN,
ServiceControl::Shutdown => Services::SERVICE_CONTROL_SHUTDOWN,
ServiceControl::Stop => Services::SERVICE_CONTROL_STOP,
ServiceControl::HardwareProfileChange(_) => {
Services::SERVICE_CONTROL_HARDWAREPROFILECHANGE
}
ServiceControl::PowerEvent(_) => Services::SERVICE_CONTROL_POWEREVENT,
ServiceControl::SessionChange(_) => Services::SERVICE_CONTROL_SESSIONCHANGE,
ServiceControl::TimeChange => Services::SERVICE_CONTROL_TIMECHANGE,
ServiceControl::TriggerEvent => Services::SERVICE_CONTROL_TRIGGEREVENT,
ServiceControl::UserEvent(event) => event.to_raw(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum ServiceState {
Stopped = Services::SERVICE_STOPPED,
StartPending = Services::SERVICE_START_PENDING,
StopPending = Services::SERVICE_STOP_PENDING,
Running = Services::SERVICE_RUNNING,
ContinuePending = Services::SERVICE_CONTINUE_PENDING,
PausePending = Services::SERVICE_PAUSE_PENDING,
Paused = Services::SERVICE_PAUSED,
}
impl ServiceState {
fn from_raw(raw: u32) -> Result<Self, ParseRawError> {
match raw {
x if x == ServiceState::Stopped.to_raw() => Ok(ServiceState::Stopped),
x if x == ServiceState::StartPending.to_raw() => Ok(ServiceState::StartPending),
x if x == ServiceState::StopPending.to_raw() => Ok(ServiceState::StopPending),
x if x == ServiceState::Running.to_raw() => Ok(ServiceState::Running),
x if x == ServiceState::ContinuePending.to_raw() => Ok(ServiceState::ContinuePending),
x if x == ServiceState::PausePending.to_raw() => Ok(ServiceState::PausePending),
x if x == ServiceState::Paused.to_raw() => Ok(ServiceState::Paused),
_ => Err(ParseRawError::InvalidInteger(raw)),
}
}
fn to_raw(self) -> u32 {
self as u32
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ServiceExitCode {
Win32(u32),
ServiceSpecific(u32),
}
impl ServiceExitCode {
pub const NO_ERROR: Self = ServiceExitCode::Win32(NO_ERROR);
fn copy_to(&self, raw_service_status: &mut Services::SERVICE_STATUS) {
match *self {
ServiceExitCode::Win32(win32_error_code) => {
raw_service_status.dwWin32ExitCode = win32_error_code;
raw_service_status.dwServiceSpecificExitCode = 0;
}
ServiceExitCode::ServiceSpecific(service_error_code) => {
raw_service_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
raw_service_status.dwServiceSpecificExitCode = service_error_code;
}
}
}
}
impl Default for ServiceExitCode {
fn default() -> Self {
Self::NO_ERROR
}
}
impl<'a> From<&'a Services::SERVICE_STATUS> for ServiceExitCode {
fn from(service_status: &'a Services::SERVICE_STATUS) -> Self {
if service_status.dwWin32ExitCode == ERROR_SERVICE_SPECIFIC_ERROR {
ServiceExitCode::ServiceSpecific(service_status.dwServiceSpecificExitCode)
} else {
ServiceExitCode::Win32(service_status.dwWin32ExitCode)
}
}
}
impl<'a> From<&'a Services::SERVICE_STATUS_PROCESS> for ServiceExitCode {
fn from(service_status: &'a Services::SERVICE_STATUS_PROCESS) -> Self {
if service_status.dwWin32ExitCode == ERROR_SERVICE_SPECIFIC_ERROR {
ServiceExitCode::ServiceSpecific(service_status.dwServiceSpecificExitCode)
} else {
ServiceExitCode::Win32(service_status.dwWin32ExitCode)
}
}
}
bitflags::bitflags! {
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
pub struct ServiceControlAccept: u32 {
const NETBIND_CHANGE = Services::SERVICE_ACCEPT_NETBINDCHANGE;
const PARAM_CHANGE = Services::SERVICE_ACCEPT_PARAMCHANGE;
const PAUSE_CONTINUE = Services::SERVICE_ACCEPT_PAUSE_CONTINUE;
const PRESHUTDOWN = Services::SERVICE_ACCEPT_PRESHUTDOWN;
const SHUTDOWN = Services::SERVICE_ACCEPT_SHUTDOWN;
const STOP = Services::SERVICE_ACCEPT_STOP;
const HARDWARE_PROFILE_CHANGE = Services::SERVICE_ACCEPT_HARDWAREPROFILECHANGE;
const POWER_EVENT = Services::SERVICE_ACCEPT_POWEREVENT;
const SESSION_CHANGE = Services::SERVICE_ACCEPT_SESSIONCHANGE;
const TIME_CHANGE = Services::SERVICE_ACCEPT_TIMECHANGE;
const TRIGGER_EVENT = Services::SERVICE_ACCEPT_TRIGGEREVENT;
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ServiceStatus {
pub service_type: ServiceType,
pub current_state: ServiceState,
pub controls_accepted: ServiceControlAccept,
pub exit_code: ServiceExitCode,
pub checkpoint: u32,
pub wait_hint: Duration,
pub process_id: Option<u32>,
}
impl ServiceStatus {
pub(crate) fn to_raw(&self) -> Services::SERVICE_STATUS {
let mut raw_status = unsafe { mem::zeroed::<Services::SERVICE_STATUS>() };
raw_status.dwServiceType = self.service_type.bits();
raw_status.dwCurrentState = self.current_state.to_raw();
raw_status.dwControlsAccepted = self.controls_accepted.bits();
self.exit_code.copy_to(&mut raw_status);
raw_status.dwCheckPoint = self.checkpoint;
raw_status.dwWaitHint =
u32::try_from(self.wait_hint.as_millis()).expect("Too long wait_hint");
raw_status
}
fn from_raw(raw: Services::SERVICE_STATUS) -> Result<Self, ParseRawError> {
Ok(ServiceStatus {
service_type: ServiceType::from_bits_truncate(raw.dwServiceType),
current_state: ServiceState::from_raw(raw.dwCurrentState)?,
controls_accepted: ServiceControlAccept::from_bits_truncate(raw.dwControlsAccepted),
exit_code: ServiceExitCode::from(&raw),
checkpoint: raw.dwCheckPoint,
wait_hint: Duration::from_millis(raw.dwWaitHint as u64),
process_id: None,
})
}
fn from_raw_ex(raw: Services::SERVICE_STATUS_PROCESS) -> Result<Self, ParseRawError> {
let current_state = ServiceState::from_raw(raw.dwCurrentState)?;
let process_id = match current_state {
ServiceState::Running => Some(raw.dwProcessId),
_ => None,
};
Ok(ServiceStatus {
service_type: ServiceType::from_bits_truncate(raw.dwServiceType),
current_state,
controls_accepted: ServiceControlAccept::from_bits_truncate(raw.dwControlsAccepted),
exit_code: ServiceExitCode::from(&raw),
checkpoint: raw.dwCheckPoint,
wait_hint: Duration::from_millis(raw.dwWaitHint as u64),
process_id,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum ServiceSidType {
None = 0,
Restricted = 3,
Unrestricted = 1,
}
pub struct Service {
service_handle: ScHandle,
}
impl Service {
pub(crate) fn new(service_handle: ScHandle) -> Self {
Service { service_handle }
}
pub fn raw_handle(&self) -> Services::SC_HANDLE {
self.service_handle.raw_handle()
}
pub fn start<S: AsRef<OsStr>>(&self, service_arguments: &[S]) -> crate::Result<()> {
let wide_service_arguments = service_arguments
.iter()
.map(|s| {
WideCString::from_os_str(s).map_err(|_| Error::ArgumentHasNulByte("start argument"))
})
.collect::<crate::Result<Vec<WideCString>>>()?;
let raw_service_arguments: Vec<*const u16> = wide_service_arguments
.iter()
.map(|s| s.as_ptr() as _)
.collect();
let success = unsafe {
Services::StartServiceW(
self.service_handle.raw_handle(),
raw_service_arguments.len() as u32,
raw_service_arguments.as_ptr(),
)
};
if success == 0 {
Err(Error::Winapi(io::Error::last_os_error()))
} else {
Ok(())
}
}
pub fn stop(&self) -> crate::Result<ServiceStatus> {
self.send_control_command(ServiceControl::Stop)
}
pub fn pause(&self) -> crate::Result<ServiceStatus> {
self.send_control_command(ServiceControl::Pause)
}
pub fn resume(&self) -> crate::Result<ServiceStatus> {
self.send_control_command(ServiceControl::Continue)
}
pub fn notify(&self, code: UserEventCode) -> crate::Result<ServiceStatus> {
self.send_control_command(ServiceControl::UserEvent(code))
}
pub fn query_status(&self) -> crate::Result<ServiceStatus> {
let mut raw_status = unsafe { mem::zeroed::<Services::SERVICE_STATUS_PROCESS>() };
let mut bytes_needed: u32 = 0;
let success = unsafe {
Services::QueryServiceStatusEx(
self.service_handle.raw_handle(),
Services::SC_STATUS_PROCESS_INFO,
&mut raw_status as *mut _ as _,
std::mem::size_of::<Services::SERVICE_STATUS_PROCESS>() as u32,
&mut bytes_needed,
)
};
if success == 0 {
Err(Error::Winapi(io::Error::last_os_error()))
} else {
ServiceStatus::from_raw_ex(raw_status)
.map_err(|e| Error::ParseValue("service status", e))
}
}
pub fn delete(&self) -> crate::Result<()> {
let success = unsafe { Services::DeleteService(self.service_handle.raw_handle()) };
if success == 0 {
Err(Error::Winapi(io::Error::last_os_error()))
} else {
Ok(())
}
}
pub fn query_config(&self) -> crate::Result<ServiceConfig> {
let mut data = vec![0u8; MAX_QUERY_BUFFER_SIZE];
let mut bytes_written: u32 = 0;
let success = unsafe {
Services::QueryServiceConfigW(
self.service_handle.raw_handle(),
data.as_mut_ptr() as _,
data.len() as u32,
&mut bytes_written,
)
};
if success == 0 {
Err(Error::Winapi(io::Error::last_os_error()))
} else {
unsafe {
let raw_config = data.as_ptr() as *const Services::QUERY_SERVICE_CONFIGW;
ServiceConfig::from_raw(*raw_config)
}
}
}
pub fn change_config(&self, service_info: &ServiceInfo) -> crate::Result<()> {
let raw_info = RawServiceInfo::new(service_info)?;
let success = unsafe {
Services::ChangeServiceConfigW(
self.service_handle.raw_handle(),
raw_info.service_type,
raw_info.start_type,
raw_info.error_control,
raw_info.launch_command.as_ptr(),
ptr::null(), ptr::null_mut(), raw_info
.dependencies
.as_ref()
.map_or(ptr::null(), |s| s.as_ptr()),
raw_info
.account_name
.as_ref()
.map_or(ptr::null(), |s| s.as_ptr()),
raw_info
.account_password
.as_ref()
.map_or(ptr::null(), |s| s.as_ptr()),
raw_info.display_name.as_ptr(),
)
};
if success == 0 {
Err(Error::Winapi(io::Error::last_os_error()))
} else {
Ok(())
}
}
pub fn set_failure_actions_on_non_crash_failures(&self, enabled: bool) -> crate::Result<()> {
let mut raw_failure_actions_flag =
unsafe { mem::zeroed::<Services::SERVICE_FAILURE_ACTIONS_FLAG>() };
raw_failure_actions_flag.fFailureActionsOnNonCrashFailures = if enabled { 1 } else { 0 };
unsafe {
self.change_config2(
Services::SERVICE_CONFIG_FAILURE_ACTIONS_FLAG,
&mut raw_failure_actions_flag,
)
.map_err(Error::Winapi)
}
}
pub fn get_failure_actions_on_non_crash_failures(&self) -> crate::Result<bool> {
let mut data = vec![0u8; MAX_QUERY_BUFFER_SIZE];
let raw_failure_actions_flag: Services::SERVICE_FAILURE_ACTIONS_FLAG = unsafe {
self.query_config2(Services::SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &mut data)
.map_err(Error::Winapi)?
};
Ok(raw_failure_actions_flag.fFailureActionsOnNonCrashFailures != 0)
}
pub fn get_config_service_sid_info(&self) -> crate::Result<ServiceSidType> {
let mut data = vec![0u8; u32::BITS as usize / 8];
unsafe { self.query_config2(Services::SERVICE_CONFIG_SERVICE_SID_INFO, &mut data) }
.map_err(Error::Winapi)
}
pub fn set_config_service_sid_info(
&self,
mut service_sid_type: ServiceSidType,
) -> crate::Result<()> {
unsafe {
self.change_config2(
Services::SERVICE_CONFIG_SERVICE_SID_INFO,
&mut service_sid_type,
)
.map_err(Error::Winapi)
}
}
pub fn get_failure_actions(&self) -> crate::Result<ServiceFailureActions> {
unsafe {
let mut data = vec![0u8; MAX_QUERY_BUFFER_SIZE];
let raw_failure_actions: Services::SERVICE_FAILURE_ACTIONSW = self
.query_config2(Services::SERVICE_CONFIG_FAILURE_ACTIONS, &mut data)
.map_err(Error::Winapi)?;
ServiceFailureActions::from_raw(raw_failure_actions)
}
}
pub fn update_failure_actions(&self, update: ServiceFailureActions) -> crate::Result<()> {
let mut raw_failure_actions =
unsafe { mem::zeroed::<Services::SERVICE_FAILURE_ACTIONSW>() };
let mut reboot_msg = to_wide_slice(update.reboot_msg)
.map_err(|_| Error::ArgumentHasNulByte("service action failures reboot message"))?;
let mut command = to_wide_slice(update.command)
.map_err(|_| Error::ArgumentHasNulByte("service action failures command"))?;
let mut sc_actions: Option<Vec<Services::SC_ACTION>> = update
.actions
.map(|actions| actions.iter().map(ServiceAction::to_raw).collect());
raw_failure_actions.dwResetPeriod = update.reset_period.to_raw();
raw_failure_actions.lpRebootMsg = reboot_msg
.as_mut()
.map_or(ptr::null_mut(), |s| s.as_mut_ptr());
raw_failure_actions.lpCommand =
command.as_mut().map_or(ptr::null_mut(), |s| s.as_mut_ptr());
raw_failure_actions.cActions = sc_actions.as_ref().map_or(0, |v| v.len()) as u32;
raw_failure_actions.lpsaActions = sc_actions
.as_mut()
.map_or(ptr::null_mut(), |actions| actions.as_mut_ptr());
unsafe {
self.change_config2(
Services::SERVICE_CONFIG_FAILURE_ACTIONS,
&mut raw_failure_actions,
)
.map_err(Error::Winapi)
}
}
pub fn set_description(&self, description: impl AsRef<OsStr>) -> crate::Result<()> {
let wide_str = WideCString::from_os_str(description)
.map_err(|_| Error::ArgumentHasNulByte("service description"))?;
let mut service_description = Services::SERVICE_DESCRIPTIONW {
lpDescription: wide_str.as_ptr() as *mut _,
};
unsafe {
self.change_config2(
Services::SERVICE_CONFIG_DESCRIPTION,
&mut service_description,
)
.map_err(Error::Winapi)
}
}
pub fn set_delayed_auto_start(&self, delayed: bool) -> crate::Result<()> {
let mut delayed = Services::SERVICE_DELAYED_AUTO_START_INFO {
fDelayedAutostart: delayed as i32,
};
unsafe {
self.change_config2(
Services::SERVICE_CONFIG_DELAYED_AUTO_START_INFO,
&mut delayed,
)
.map_err(Error::Winapi)
}
}
pub fn set_preshutdown_timeout(&self, timeout: Duration) -> crate::Result<()> {
let mut timeout = Services::SERVICE_PRESHUTDOWN_INFO {
dwPreshutdownTimeout: u32::try_from(timeout.as_millis()).expect("Too long timeout"),
};
unsafe {
self.change_config2(Services::SERVICE_CONFIG_PRESHUTDOWN_INFO, &mut timeout)
.map_err(Error::Winapi)
}
}
fn send_control_command(&self, command: ServiceControl) -> crate::Result<ServiceStatus> {
let mut raw_status = unsafe { mem::zeroed::<Services::SERVICE_STATUS>() };
let success = unsafe {
Services::ControlService(
self.service_handle.raw_handle(),
command.raw_service_control_type(),
&mut raw_status,
)
};
if success == 0 {
Err(Error::Winapi(io::Error::last_os_error()))
} else {
ServiceStatus::from_raw(raw_status).map_err(|e| Error::ParseValue("service status", e))
}
}
unsafe fn query_config2<T: Copy>(&self, kind: u32, data: &mut [u8]) -> io::Result<T> {
let mut bytes_written: u32 = 0;
let success = Services::QueryServiceConfig2W(
self.service_handle.raw_handle(),
kind,
data.as_mut_ptr() as _,
data.len() as u32,
&mut bytes_written,
);
if success == 0 {
Err(io::Error::last_os_error())
} else {
Ok(*(data.as_ptr() as *const _))
}
}
unsafe fn change_config2<T>(&self, kind: u32, data: &mut T) -> io::Result<()> {
let success = Services::ChangeServiceConfig2W(
self.service_handle.raw_handle(),
kind,
data as *mut _ as *mut _,
);
if success == 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
}
const MAX_QUERY_BUFFER_SIZE: usize = 8 * 1024;
fn to_wide_slice(
s: Option<impl AsRef<OsStr>>,
) -> ::std::result::Result<Option<Vec<u16>>, ContainsNul<u16>> {
if let Some(s) = s {
Ok(Some(
WideCString::from_os_str(s).map(|s| s.into_vec_with_nul())?,
))
} else {
Ok(None)
}
}
#[derive(Debug)]
pub enum ParseRawError {
InvalidInteger(u32),
InvalidIntegerSigned(i32),
InvalidGuid(String),
}
impl std::error::Error for ParseRawError {}
impl std::fmt::Display for ParseRawError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidInteger(u) => {
write!(f, "invalid unsigned integer for the target type: {}", u)
}
Self::InvalidIntegerSigned(i) => {
write!(f, "invalid signed integer for the target type: {}", i)
}
Self::InvalidGuid(guid) => {
write!(f, "invalid GUID value for the target type: {}", guid)
}
}
}
}
fn string_from_guid(guid: &GUID) -> String {
format!(
"{:8X}-{:4X}-{:4X}-{:2X}{:2X}-{:2X}{:2X}{:2X}{:2X}{:2X}{:2X}",
guid.data1,
guid.data2,
guid.data3,
guid.data4[0],
guid.data4[1],
guid.data4[2],
guid.data4[3],
guid.data4[4],
guid.data4[5],
guid.data4[6],
guid.data4[7]
)
}
pub(crate) fn to_wide(
s: Option<impl AsRef<OsStr>>,
) -> ::std::result::Result<Option<WideCString>, ContainsNul<u16>> {
if let Some(s) = s {
Ok(Some(WideCString::from_os_str(s)?))
} else {
Ok(None)
}
}
fn escape_wide(s: impl AsRef<OsStr>) -> ::std::result::Result<WideString, ContainsNul<u16>> {
let escaped = shell_escape::escape(Cow::Borrowed(s.as_ref()));
let wide = WideCString::from_os_str(escaped)?;
Ok(wide.to_ustring())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_service_group_identifier() {
let dependency = ServiceDependency::from_system_identifier("+network");
assert_eq!(
dependency,
ServiceDependency::Group(OsString::from("network"))
);
}
#[test]
fn test_service_name_identifier() {
let dependency = ServiceDependency::from_system_identifier("netlogon");
assert_eq!(
dependency,
ServiceDependency::Service(OsString::from("netlogon"))
);
}
}