diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2018-06-01 15:40:35 +0200 |
|---|---|---|
| committer | Andrej Mihajlov <and.mikhaylov@gmail.com> | 2018-06-04 14:52:04 +0200 |
| commit | 7318786327ef2d9f14e5814a12bcf43da40feca0 (patch) | |
| tree | db3d7ac9376b2805f0102aff9cecf9a9baafb182 | |
| parent | 05405300140d1c20e2a843378a334953ddb1c5bb (diff) | |
| download | mullvadvpn-7318786327ef2d9f14e5814a12bcf43da40feca0.tar.xz mullvadvpn-7318786327ef2d9f14e5814a12bcf43da40feca0.zip | |
Remove local source of windows-service and migrate to mullvad/windows-service-rs
| -rw-r--r-- | Cargo.lock | 16 | ||||
| -rw-r--r-- | Cargo.toml | 1 | ||||
| -rw-r--r-- | mullvad-daemon/Cargo.toml | 2 | ||||
| -rw-r--r-- | mullvad-daemon/src/system_service.rs | 7 | ||||
| -rw-r--r-- | windows-service/Cargo.toml | 15 | ||||
| -rw-r--r-- | windows-service/examples/simple_service.rs | 457 | ||||
| -rw-r--r-- | windows-service/src/lib.rs | 18 | ||||
| -rw-r--r-- | windows-service/src/service.rs | 396 | ||||
| -rw-r--r-- | windows-service/src/service_control_handler.rs | 144 | ||||
| -rw-r--r-- | windows-service/src/service_dispatcher.rs | 90 | ||||
| -rw-r--r-- | windows-service/src/service_manager.rs | 196 | ||||
| -rw-r--r-- | windows-service/src/shell_escape.rs | 120 |
12 files changed, 7 insertions, 1455 deletions
diff --git a/Cargo.lock b/Cargo.lock index de9aa030a5..efba8d2afc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -782,7 +782,7 @@ dependencies = [ "tokio-core 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-timer 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", - "windows-service 0.1.0", + "windows-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1285,15 +1285,6 @@ dependencies = [ ] [[package]] -name = "simplelog" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "chrono 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] name = "slab" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1811,11 +1802,10 @@ dependencies = [ [[package]] name = "windows-service" version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "simplelog 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "widestring 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1987,7 +1977,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum shell-escape 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "170a13e64f2a51b77a45702ba77287f5c6829375b04a69cf2222acd17d0cfab9" "checksum shell32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9ee04b46101f57121c9da2b151988283b6beb79b34f5bb29a58ee48cb695122c" "checksum simple-signal 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "53f7da44adcc42667d57483bd93f81295f27d66897804b757573b61b6f13288b" -"checksum simplelog 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ce595117de34b75e057b41e99079e43e9fcc4e5ec9c7ba5f2fea55321f0c624e" "checksum slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d807fd58c4181bbabed77cb3b891ba9748241a552bcc5be698faaebefc54f46e" "checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23" "checksum slab 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fdeff4cd9ecff59ec7e3744cbca73dfe5ac35c2aedb2cfba8a1c715a18912e9d" @@ -2044,6 +2033,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" "checksum wincolor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "eeb06499a3a4d44302791052df005d5232b927ed1a9658146d842165c4de7767" +"checksum windows-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "51cb08429e18f787748343122610b09f50c79f8034627e57faacf37582a709ec" "checksum ws 0.7.5 (git+https://github.com/tomusdrw/ws-rs)" = "<none>" "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" "checksum xdg 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a66b7c2281ebde13cf4391d70d4c7e5946c3c25e72a7b859ca8f677dcd0b0c61" diff --git a/Cargo.toml b/Cargo.toml index 8bcae5cce6..5879f8ed18 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,6 @@ [workspace] members = [ "mullvad-daemon", - "windows-service", "mullvad-cli", "mullvad-ipc-client", "mullvad-metadata", diff --git a/mullvad-daemon/Cargo.toml b/mullvad-daemon/Cargo.toml index b09db7e275..da13e356c2 100644 --- a/mullvad-daemon/Cargo.toml +++ b/mullvad-daemon/Cargo.toml @@ -41,7 +41,7 @@ simple-signal = "1.1" [target.'cfg(windows)'.dependencies] ctrlc = "3.0" -windows-service = { path = "../windows-service" } +windows-service = "0.1" [dev-dependencies] assert_matches = "1.0" diff --git a/mullvad-daemon/src/system_service.rs b/mullvad-daemon/src/system_service.rs index 0cc53f3522..91e245e881 100644 --- a/mullvad-daemon/src/system_service.rs +++ b/mullvad-daemon/src/system_service.rs @@ -31,7 +31,7 @@ pub fn run() -> Result<()> { // Start the service dispatcher. // This will block current thread until the service stopped and spawn `service_main` on a // background thread. - service_dispatcher::start_dispatcher(SERVICE_NAME, service_main) + service_dispatcher::start(SERVICE_NAME, service_main) .chain_err(|| "Failed to start a service dispatcher") } @@ -64,9 +64,8 @@ fn run_service(_arguments: Vec<OsString>) -> Result<()> { _ => ServiceControlHandlerResult::NotImplemented, } }; - let status_handle = - service_control_handler::register_control_handler(SERVICE_NAME, event_handler) - .chain_err(|| "Failed to register a service control handler")?; + let status_handle = service_control_handler::register(SERVICE_NAME, event_handler) + .chain_err(|| "Failed to register a service control handler")?; let mut persistent_service_status = PersistentServiceStatus::new(status_handle); persistent_service_status .set_pending_start(Duration::from_secs(1)) diff --git a/windows-service/Cargo.toml b/windows-service/Cargo.toml deleted file mode 100644 index 0e73261ce8..0000000000 --- a/windows-service/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "windows-service" -version = "0.1.0" -authors = ["Mullvad VPN <admin@mullvad.net>", "Andrej Mihajlov <and@mullvad.net>"] - -[target.'cfg(windows)'.dependencies] -bitflags = "1.0.1" -error-chain = "0.11" -winapi = { version = "0.3", features = ["std", "winsvc", "winerror"] } -widestring = "0.3.0" - -[dev-dependencies] -error-chain = "0.11" -log = "0.4" -simplelog = { version = "0.5", default-features = false } diff --git a/windows-service/examples/simple_service.rs b/windows-service/examples/simple_service.rs deleted file mode 100644 index ee72ddbe73..0000000000 --- a/windows-service/examples/simple_service.rs +++ /dev/null @@ -1,457 +0,0 @@ -// Simple service example. -// -// All commands mentioned below shall be executed in Command Prompt with Administrator privileges. -// -// Service self-installation: `simple_service.exe --install-service` -// Service self-removal: `simple_service.exe --remove-service` -// -// Start the service: `net start simpleservice` -// Pause the service: `net pause simpleservice` -// Resume the service: `net continue simpleservice` -// Stop the service: `net stop simpleservice` -// -// Simple service outputs all logs in C:\Windows\Temp\simple-service.log. -// If you have GNU tools installed, you can follow the log using: -// `tail -F C:\Windows\Temp\simple-service.log` -// - -#[cfg(windows)] -#[macro_use] -extern crate error_chain; -#[cfg(windows)] -#[macro_use] -extern crate log; -#[cfg(windows)] -#[macro_use] -extern crate windows_service; - -#[cfg(not(windows))] -fn main() { - panic!("This program is only intended to run on Windows."); -} - -#[cfg(windows)] -fn main() { - simple_service::run(); -} - -#[cfg(windows)] -mod simple_service { - extern crate simplelog; - - use std::ffi::OsString; - use std::fs::OpenOptions; - use std::path::PathBuf; - use std::sync::atomic::{AtomicUsize, Ordering}; - use std::sync::mpsc; - use std::time::Duration; - use std::{env, io, thread, time}; - - use self::simplelog::{CombinedLogger, Config, WriteLogger}; - use log::LevelFilter; - - use windows_service::service::{ - ServiceAccess, ServiceControl, ServiceControlAccept, ServiceErrorControl, ServiceExitCode, - ServiceInfo, ServiceStartType, ServiceState, ServiceStatus, ServiceType, - }; - use windows_service::service_control_handler::{ - self, ServiceControlHandlerResult, ServiceStatusHandle, - }; - use windows_service::service_dispatcher; - use windows_service::service_manager::{ServiceManager, ServiceManagerAccess}; - use windows_service::ChainedError; - - static SERVICE_NAME: &'static str = "SimpleService"; - static SERVICE_DISPLAY_NAME: &'static str = "Simple Service"; - - error_chain! { - errors { - InstallService { - description("Failed to install the service") - } - RemoveService { - description("Failed to remove the service") - } - OpenLogFile(path: PathBuf) { - description("Unable to open log file for writing") - display("Unable to open log file for writing: {}", path.display()) - } - InitLogger { - description("Cannot initialize logger") - } - } - foreign_links { - SetLoggerError(::log::SetLoggerError); - } - } - - static CHECKPOINT_COUNTER: AtomicUsize = AtomicUsize::new(0); - - pub fn update_service_status( - status_handle: &ServiceStatusHandle, - next_state: ServiceState, - exit_code: ServiceExitCode, - wait_hint: Duration, - ) -> io::Result<()> { - // Automatically bump the checkpoint when updating the pending events to tell the system - // that the service is making a progress in transition from pending to final state. - // `wait_hint` should reflect the estimated time for transition to complete. - let checkpoint = match next_state { - ServiceState::ContinuePending - | ServiceState::PausePending - | ServiceState::StartPending - | ServiceState::StopPending => CHECKPOINT_COUNTER.fetch_add(1, Ordering::SeqCst) + 1, - _ => 0, - }; - let service_status = ServiceStatus { - service_type: ServiceType::OwnProcess, - current_state: next_state, - controls_accepted: accepted_controls_by_state(next_state), - exit_code: exit_code, - checkpoint: checkpoint as u32, - wait_hint: wait_hint, - }; - info!( - "Update service status: {:?}, checkpoint: {}, wait_hint: {:?}", - service_status.current_state, service_status.checkpoint, service_status.wait_hint - ); - status_handle.set_service_status(service_status) - } - - pub fn run() { - if let Some(command) = env::args().nth(1) { - match command.as_ref() { - "--install-service" => { - if let Err(e) = install_service() { - println!("{}", e.display_chain()); - } else { - println!("Installed the service."); - } - } - "--remove-service" => { - if let Err(e) = remove_service() { - println!("{}", e.display_chain()); - } else { - println!("Removed the service."); - } - } - "--run-service" => { - // Setup file logger since there is no stdout when running as a service. - if let Err(err) = init_logger() { - panic!("Unable to initialize logger: {}", err.display_chain()); - } - - // Start the service dispatcher. - // This will block current thread until the service stopped. - let result = service_dispatcher::start_dispatcher(SERVICE_NAME, service_main); - - match result { - Err(ref e) => { - error!("Failed to start service dispatcher: {}", e.display_chain()); - } - Ok(_) => { - info!("Service dispatcher exited."); - } - }; - } - _ => println!("Unsupported command: {}", command), - } - } else { - println!("Usage:"); - println!("--install-service to install the service"); - println!("--remove-service to uninstall the service"); - println!("--run-service to run the service"); - } - } - - define_windows_service!(service_main, handle_service_main); - - pub fn handle_service_main(arguments: Vec<OsString>) { - // Create an event channel to funnel events to worker. - let (event_tx, event_rx) = mpsc::channel(); - - info!("Received arguments: {:?}", arguments); - - // Register service event handler - let event_handler = move |control_event| -> ServiceControlHandlerResult { - match control_event { - // Notifies a service to report its current status information to the service - // control manager. Always return NO_ERROR even if not implemented. - ServiceControl::Interrogate => ServiceControlHandlerResult::NoError, - - // Handle primary control events - ServiceControl::Pause - | ServiceControl::Continue - | ServiceControl::Stop - | ServiceControl::Shutdown => { - event_tx.send(control_event).unwrap(); - ServiceControlHandlerResult::NoError - } - - _ => ServiceControlHandlerResult::NotImplemented, - } - }; - - let result = service_control_handler::register_control_handler(SERVICE_NAME, event_handler); - match result { - Ok(status_handle) => { - run_service(status_handle, event_rx); - } - Err(ref e) => { - error!("Cannot register a service control handler: {}", e); - } - }; - - info!("Quit service main."); - } - - #[derive(Debug, Copy, Clone)] - enum DaemonEvent { - Continue, - Pause, - Stop, - } - - fn start_event_monitor( - service_status_handle: ServiceStatusHandle, - event_rx: mpsc::Receiver<ServiceControl>, - daemon_tx: mpsc::Sender<DaemonEvent>, - ) -> thread::JoinHandle<()> { - thread::spawn(move || { - loop { - match event_rx.recv().unwrap() { - ServiceControl::Pause => { - info!("Pausing the service."); - - update_service_status( - &service_status_handle, - ServiceState::PausePending, - ServiceExitCode::Win32(0), - Duration::from_secs(2), - ).unwrap(); - - daemon_tx.send(DaemonEvent::Pause).unwrap(); - } - - ServiceControl::Continue => { - info!("Continuing the service."); - - update_service_status( - &service_status_handle, - ServiceState::ContinuePending, - ServiceExitCode::Win32(0), - Duration::from_secs(2), - ).unwrap(); - - daemon_tx.send(DaemonEvent::Continue).unwrap(); - } - - ServiceControl::Stop => { - info!("Stopping the service."); - - update_service_status( - &service_status_handle, - ServiceState::StopPending, - ServiceExitCode::Win32(0), - Duration::from_secs(2), - ).unwrap(); - - daemon_tx.send(DaemonEvent::Stop).unwrap(); - break; // break the loop - } - - ServiceControl::Shutdown => { - info!("Exiting due to shutdown."); - - update_service_status( - &service_status_handle, - ServiceState::StopPending, - ServiceExitCode::Win32(0), - Duration::from_secs(1), - ).unwrap(); - - daemon_tx.send(DaemonEvent::Stop).unwrap(); - break; // break the loop - } - - _ => (), - }; - } - }) - } - - fn start_worker( - service_status_handle: ServiceStatusHandle, - daemon_rx: mpsc::Receiver<DaemonEvent>, - ) -> thread::JoinHandle<()> { - thread::spawn(move || { - let mut is_running = true; - let mut is_paused = false; - - // Tell Windows that the service is running now - update_service_status( - &service_status_handle, - ServiceState::Running, - ServiceExitCode::Win32(0), - Duration::default(), - ).unwrap(); - - while is_running { - // Do some work - if !is_paused { - info!("Working..."); - } - - // Poll events - match daemon_rx.recv_timeout(Duration::from_secs(1)) { - Ok(DaemonEvent::Pause) => { - is_paused = true; - - update_service_status( - &service_status_handle, - ServiceState::Paused, - ServiceExitCode::Win32(0), - Duration::default(), - ).unwrap(); - } - Ok(DaemonEvent::Continue) => { - is_paused = false; - - update_service_status( - &service_status_handle, - ServiceState::Running, - ServiceExitCode::Win32(0), - Duration::default(), - ).unwrap(); - } - Ok(DaemonEvent::Stop) | Err(mpsc::RecvTimeoutError::Disconnected) => { - is_running = false; - - update_service_status( - &service_status_handle, - ServiceState::Stopped, - ServiceExitCode::Win32(0), - Duration::default(), - ).unwrap(); - } - Err(mpsc::RecvTimeoutError::Timeout) => (), - }; - } - }) - } - - fn run_service(status_handle: ServiceStatusHandle, event_rx: mpsc::Receiver<ServiceControl>) { - let (daemon_tx, daemon_rx) = mpsc::channel(); - - // Tell Windows that the service is starting up - update_service_status( - &status_handle, - ServiceState::StartPending, - ServiceExitCode::Win32(0), - Duration::from_secs(5), - ).unwrap(); - - let event_monitor_handle = start_event_monitor(status_handle, event_rx, daemon_tx); - let worker_thread_handle = start_worker(status_handle, daemon_rx); - - // Block current thread until other threads complete execution - event_monitor_handle.join().unwrap(); - worker_thread_handle.join().unwrap(); - } - - /// Returns the list of accepted service events at each stage of the service lifecycle. - fn accepted_controls_by_state(state: ServiceState) -> ServiceControlAccept { - match state { - ServiceState::StartPending - | ServiceState::PausePending - | ServiceState::ContinuePending => ServiceControlAccept::empty(), - ServiceState::Running => { - ServiceControlAccept::STOP - | ServiceControlAccept::PAUSE_CONTINUE - | ServiceControlAccept::SHUTDOWN - } - ServiceState::Paused => { - ServiceControlAccept::STOP - | ServiceControlAccept::PAUSE_CONTINUE - | ServiceControlAccept::SHUTDOWN - } - ServiceState::StopPending | ServiceState::Stopped => ServiceControlAccept::empty(), - } - } - - fn install_service() -> Result<()> { - let manager_access = ServiceManagerAccess::CONNECT | ServiceManagerAccess::CREATE_SERVICE; - let service_manager = ServiceManager::local_computer(None::<&str>, manager_access) - .chain_err(|| ErrorKind::InstallService)?; - let service_info = get_service_info(); - service_manager - .create_service(service_info, ServiceAccess::empty()) - .map(|_| ()) - .chain_err(|| ErrorKind::InstallService) - } - - fn remove_service() -> Result<()> { - let manager_access = ServiceManagerAccess::CONNECT; - let service_manager = ServiceManager::local_computer(None::<&str>, manager_access) - .chain_err(|| ErrorKind::RemoveService)?; - - let service_access = - ServiceAccess::QUERY_STATUS | ServiceAccess::STOP | ServiceAccess::DELETE; - let service = service_manager - .open_service(SERVICE_NAME, service_access) - .chain_err(|| ErrorKind::RemoveService)?; - - loop { - let service_status = service - .query_status() - .chain_err(|| ErrorKind::RemoveService)?; - - match service_status.current_state { - ServiceState::StopPending => (), - ServiceState::Stopped => { - println!("Removing the service..."); - service.delete().chain_err(|| ErrorKind::RemoveService)?; - return Ok(()); // explicit return - } - _ => { - println!("Stopping the service..."); - service.stop().chain_err(|| ErrorKind::RemoveService)?; - } - } - - thread::sleep(time::Duration::from_secs(1)) - } - } - - fn get_service_info() -> ServiceInfo { - ServiceInfo { - name: OsString::from(SERVICE_NAME), - display_name: OsString::from(SERVICE_DISPLAY_NAME), - service_type: ServiceType::OwnProcess, - start_type: ServiceStartType::OnDemand, - error_control: ServiceErrorControl::Normal, - executable_path: env::current_exe().unwrap(), - launch_arguments: vec![OsString::from("--run-service")], - account_name: None, // run as System - account_password: None, - } - } - - fn init_logger() -> Result<()> { - let windows_directory = env::var_os("WINDIR").unwrap(); - let log_file_path = PathBuf::from(windows_directory) - .join("Temp") - .join("simple-service.log"); - - let log_file = OpenOptions::new() - .create(true) - .append(true) - .open(log_file_path.as_path()) - .chain_err(|| ErrorKind::OpenLogFile(log_file_path))?; - - let file_logger = WriteLogger::new(LevelFilter::Trace, Config::default(), log_file); - - CombinedLogger::init(vec![file_logger]).chain_err(|| ErrorKind::InitLogger) - } - -} diff --git a/windows-service/src/lib.rs b/windows-service/src/lib.rs deleted file mode 100644 index bae17eb41d..0000000000 --- a/windows-service/src/lib.rs +++ /dev/null @@ -1,18 +0,0 @@ -#![cfg(windows)] - -#[macro_use] -extern crate bitflags; -#[macro_use] -extern crate error_chain; -extern crate widestring; -extern crate winapi; - -pub use error_chain::ChainedError; - -pub mod service; -pub mod service_control_handler; -pub mod service_manager; -#[macro_use] -pub mod service_dispatcher; - -mod shell_escape; diff --git a/windows-service/src/service.rs b/windows-service/src/service.rs deleted file mode 100644 index 1898cf8c1c..0000000000 --- a/windows-service/src/service.rs +++ /dev/null @@ -1,396 +0,0 @@ -use std::ffi::OsString; -use std::path::PathBuf; -use std::time::Duration; -use std::{io, mem}; - -use winapi::shared::winerror::{ERROR_SERVICE_SPECIFIC_ERROR, NO_ERROR}; -use winapi::um::{winnt, winsvc}; - -mod errors { - error_chain! { - errors { - InvalidServiceType(raw_value: u32) { - description("Invalid service type value") - display("Invalid service type value: {}", raw_value) - } - InvalidServiceState(raw_value: u32) { - description("Invalid service state") - display("Invalid service state value: {}", raw_value) - } - InvalidServiceControl(raw_value: u32) { - description("Invalid service control") - display("Invalid service control value: {}", raw_value) - } - } - foreign_links { - System(::std::io::Error); - } - } -} -pub use self::errors::*; - -/// Enum describing types of windows services -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[repr(u32)] -pub enum ServiceType { - /// Service that runs in its own process. - OwnProcess = winnt::SERVICE_WIN32_OWN_PROCESS, -} - -impl ServiceType { - pub fn from_raw(raw_value: u32) -> Result<Self> { - let service_type = match raw_value { - x if x == ServiceType::OwnProcess.to_raw() => ServiceType::OwnProcess, - _ => Err(ErrorKind::InvalidServiceType(raw_value))?, - }; - Ok(service_type) - } - - pub fn to_raw(&self) -> u32 { - *self as u32 - } -} - -/// Flags describing the access permissions when working with services -bitflags! { - pub struct ServiceAccess: u32 { - /// Can query the service status - const QUERY_STATUS = winsvc::SERVICE_QUERY_STATUS; - - /// Can start the service - const START = winsvc::SERVICE_START; - - // Can stop the service - const STOP = winsvc::SERVICE_STOP; - - /// Can pause or continue the service execution - const PAUSE_CONTINUE = winsvc::SERVICE_PAUSE_CONTINUE; - - /// Can ask the service to report its status - const INTERROGATE = winsvc::SERVICE_INTERROGATE; - - /// Can delete the service - const DELETE = winnt::DELETE; - } -} - -/// Enum describing the start options for windows services -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[repr(u32)] -pub enum ServiceStartType { - /// Autostart on system startup - AutoStart = winnt::SERVICE_AUTO_START, - /// Service is enabled, can be started manually - OnDemand = winnt::SERVICE_DEMAND_START, - /// Disabled service - Disabled = winnt::SERVICE_DISABLED, -} - -impl ServiceStartType { - pub fn to_raw(&self) -> u32 { - *self as u32 - } -} - -/// Error handling strategy for service failures. -/// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682450(v=vs.85).aspx -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[repr(u32)] -pub enum ServiceErrorControl { - Critical = winnt::SERVICE_ERROR_CRITICAL, - Ignore = winnt::SERVICE_ERROR_IGNORE, - Normal = winnt::SERVICE_ERROR_NORMAL, - Severe = winnt::SERVICE_ERROR_SEVERE, -} - -impl ServiceErrorControl { - pub fn to_raw(&self) -> u32 { - *self as u32 - } -} - -/// A struct that describes the service -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct ServiceInfo { - /// Service name - pub name: OsString, - - /// Friendly service name - pub display_name: OsString, - - pub service_type: ServiceType, - pub start_type: ServiceStartType, - pub error_control: ServiceErrorControl, - - /// Path to the service binary. - pub executable_path: PathBuf, - - /// Launch arguments passed to `main` when system starts the service. - /// This is not the same as arguments passed to `service_main`. - pub launch_arguments: Vec<OsString>, - - /// Account to use for running the service. - /// for example: NT Authority\System. - /// use `None` to run as LocalSystem. - pub account_name: Option<OsString>, - - /// Account password. - /// For system accounts this should normally be `None`. - pub account_password: Option<OsString>, -} - -/// Enum describing the service control operations -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[repr(u32)] -pub enum ServiceControl { - Continue = winsvc::SERVICE_CONTROL_CONTINUE, - Interrogate = winsvc::SERVICE_CONTROL_INTERROGATE, - NetBindAdd = winsvc::SERVICE_CONTROL_NETBINDADD, - NetBindDisable = winsvc::SERVICE_CONTROL_NETBINDDISABLE, - NetBindEnable = winsvc::SERVICE_CONTROL_NETBINDENABLE, - NetBindRemove = winsvc::SERVICE_CONTROL_NETBINDREMOVE, - ParamChange = winsvc::SERVICE_CONTROL_PARAMCHANGE, - Pause = winsvc::SERVICE_CONTROL_PAUSE, - Preshutdown = winsvc::SERVICE_CONTROL_PRESHUTDOWN, - Shutdown = winsvc::SERVICE_CONTROL_SHUTDOWN, - Stop = winsvc::SERVICE_CONTROL_STOP, -} - -impl ServiceControl { - pub fn from_raw(raw_value: u32) -> Result<Self> { - let service_control = match raw_value { - x if x == ServiceControl::Continue.to_raw() => ServiceControl::Continue, - x if x == ServiceControl::Interrogate.to_raw() => ServiceControl::Interrogate, - x if x == ServiceControl::NetBindAdd.to_raw() => ServiceControl::NetBindAdd, - x if x == ServiceControl::NetBindDisable.to_raw() => ServiceControl::NetBindDisable, - x if x == ServiceControl::NetBindEnable.to_raw() => ServiceControl::NetBindEnable, - x if x == ServiceControl::NetBindRemove.to_raw() => ServiceControl::NetBindRemove, - x if x == ServiceControl::ParamChange.to_raw() => ServiceControl::ParamChange, - x if x == ServiceControl::Pause.to_raw() => ServiceControl::Pause, - x if x == ServiceControl::Preshutdown.to_raw() => ServiceControl::Preshutdown, - x if x == ServiceControl::Shutdown.to_raw() => ServiceControl::Shutdown, - x if x == ServiceControl::Stop.to_raw() => ServiceControl::Stop, - other => Err(ErrorKind::InvalidServiceControl(other))?, - }; - Ok(service_control) - } - - pub fn to_raw(&self) -> u32 { - *self as u32 - } -} - -/// Service state returned as a part of ServiceStatus -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[repr(u32)] -pub enum ServiceState { - Stopped = winsvc::SERVICE_STOPPED, - StartPending = winsvc::SERVICE_START_PENDING, - StopPending = winsvc::SERVICE_STOP_PENDING, - Running = winsvc::SERVICE_RUNNING, - ContinuePending = winsvc::SERVICE_CONTINUE_PENDING, - PausePending = winsvc::SERVICE_PAUSE_PENDING, - Paused = winsvc::SERVICE_PAUSED, -} - -impl ServiceState { - fn from_raw(raw_state: u32) -> Result<Self> { - let service_state = match raw_state { - x if x == ServiceState::Stopped.to_raw() => ServiceState::Stopped, - x if x == ServiceState::StartPending.to_raw() => ServiceState::StartPending, - x if x == ServiceState::StopPending.to_raw() => ServiceState::StopPending, - x if x == ServiceState::Running.to_raw() => ServiceState::Running, - x if x == ServiceState::ContinuePending.to_raw() => ServiceState::ContinuePending, - x if x == ServiceState::PausePending.to_raw() => ServiceState::PausePending, - x if x == ServiceState::Paused.to_raw() => ServiceState::Paused, - other => Err(ErrorKind::InvalidServiceState(other))?, - }; - Ok(service_state) - } - - fn to_raw(&self) -> u32 { - *self as u32 - } -} - -/// Service exit code abstraction. -/// -/// This struct provides a logic around the relationship between `win32_exit_code` and -/// `service_specific_exit_code`. -/// -/// The service can either return a win32 error code or a custom error -/// code. In that case `win32_exit_code` has to be set to `ERROR_SERVICE_SPECIFIC_ERROR` and -/// the `service_specific_exit_code` assigned with custom error code. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum ServiceExitCode { - Win32(u32), - ServiceSpecific(u32), -} - -impl ServiceExitCode { - fn copy_to(&self, raw_service_status: &mut winsvc::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 { - ServiceExitCode::Win32(NO_ERROR) - } -} - -impl<'a> From<&'a winsvc::SERVICE_STATUS> for ServiceExitCode { - fn from(service_status: &'a winsvc::SERVICE_STATUS) -> Self { - if service_status.dwWin32ExitCode == ERROR_SERVICE_SPECIFIC_ERROR { - ServiceExitCode::ServiceSpecific(service_status.dwServiceSpecificExitCode) - } else { - ServiceExitCode::Win32(service_status.dwWin32ExitCode) - } - } -} - -/// Flags describing accepted types of service control requests -bitflags! { - pub struct ServiceControlAccept: u32 { - /// The service is a network component that can accept changes in its binding without being - /// stopped and restarted. This allows service to receive `ServiceControl::Netbind*` - /// family of events. - const NETBIND_CHANGE = winsvc::SERVICE_ACCEPT_NETBINDCHANGE; - - /// The service can reread its startup parameters without being stopped and restarted. - const PARAM_CHANGE = winsvc::SERVICE_ACCEPT_PARAMCHANGE; - - /// The service can be paused and continued. - const PAUSE_CONTINUE = winsvc::SERVICE_ACCEPT_PAUSE_CONTINUE; - - /// The service can perform preshutdown tasks. - /// Mutually exclusive with shutdown. - const PRESHUTDOWN = winsvc::SERVICE_ACCEPT_PRESHUTDOWN; - - /// The service is notified when system shutdown occurs. - /// Mutually exclusive with preshutdown. - const SHUTDOWN = winsvc::SERVICE_ACCEPT_SHUTDOWN; - - /// The service can be stopped. - const STOP = winsvc::SERVICE_ACCEPT_STOP; - } -} - -/// Service status. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct ServiceStatus { - /// Type of service - pub service_type: ServiceType, - - /// Current state of the service - pub current_state: ServiceState, - - /// Control commands that service accepts. - pub controls_accepted: ServiceControlAccept, - - /// Service exit code - pub exit_code: ServiceExitCode, - - /// Service initialization progress value that should be increased during a lengthy start, - /// stop, pause or continue eration. For example the service should increment the value as - /// it completes each step of initialization. - /// This value must be zero if the service does not have any pending start, stop, pause or - /// continue operations. - pub checkpoint: u32, - - /// Estimated time for pending operation. - /// This basically works as a timeout until the service manager assumes that the service hung. - /// This could be either circumvented by updating the `current_state` or incrementing a - /// `checkpoint` value. - pub wait_hint: Duration, -} - -impl ServiceStatus { - pub(super) fn to_raw(&self) -> winsvc::SERVICE_STATUS { - let mut raw_status = unsafe { mem::zeroed::<winsvc::SERVICE_STATUS>() }; - raw_status.dwServiceType = self.service_type.to_raw(); - 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; - - // we lose precision here but dwWaitHint should never be too big. - raw_status.dwWaitHint = (self.wait_hint.as_secs() * 1000) as u32; - - raw_status - } - - fn from_raw(raw_status: winsvc::SERVICE_STATUS) -> Result<Self> { - Ok(ServiceStatus { - service_type: ServiceType::from_raw(raw_status.dwServiceType)?, - current_state: ServiceState::from_raw(raw_status.dwCurrentState)?, - controls_accepted: ServiceControlAccept::from_bits_truncate( - raw_status.dwControlsAccepted, - ), - exit_code: ServiceExitCode::from(&raw_status), - checkpoint: raw_status.dwCheckPoint, - wait_hint: Duration::from_millis(raw_status.dwWaitHint as u64), - }) - } -} - - -pub struct Service(winsvc::SC_HANDLE); - -impl Service { - /// Internal constructor - pub(super) unsafe fn from_handle(handle: winsvc::SC_HANDLE) -> Self { - Service(handle) - } - - pub fn stop(&self) -> Result<ServiceStatus> { - self.send_control_command(ServiceControl::Stop) - } - - pub fn query_status(&self) -> Result<ServiceStatus> { - let mut raw_status = unsafe { mem::zeroed::<winsvc::SERVICE_STATUS>() }; - let success = unsafe { winsvc::QueryServiceStatus(self.0, &mut raw_status) }; - if success == 1 { - ServiceStatus::from_raw(raw_status) - } else { - Err(io::Error::last_os_error().into()) - } - } - - pub fn delete(self) -> io::Result<()> { - let success = unsafe { winsvc::DeleteService(self.0) }; - if success == 1 { - Ok(()) - } else { - Err(io::Error::last_os_error()) - } - } - - fn send_control_command(&self, command: ServiceControl) -> Result<ServiceStatus> { - let mut raw_status = unsafe { mem::zeroed::<winsvc::SERVICE_STATUS>() }; - let success = unsafe { winsvc::ControlService(self.0, command.to_raw(), &mut raw_status) }; - - if success == 1 { - ServiceStatus::from_raw(raw_status).map_err(|err| err.into()) - } else { - Err(io::Error::last_os_error().into()) - } - } -} - -impl Drop for Service { - fn drop(&mut self) { - unsafe { winsvc::CloseServiceHandle(self.0) }; - } -} diff --git a/windows-service/src/service_control_handler.rs b/windows-service/src/service_control_handler.rs deleted file mode 100644 index b722e94f52..0000000000 --- a/windows-service/src/service_control_handler.rs +++ /dev/null @@ -1,144 +0,0 @@ -use std::ffi::OsStr; -use std::io; -use widestring::WideCString; -use winapi::shared::winerror::{ERROR_CALL_NOT_IMPLEMENTED, NO_ERROR}; -use winapi::um::winsvc; - -use service::{ServiceControl, ServiceStatus}; - -mod errors { - error_chain! { - errors { - InvalidServiceName { - description("Invalid service name") - } - } - foreign_links { - System(::std::io::Error); - } - } -} -pub use self::errors::*; - -/// Struct that holds unique token for updating the status of the corresponding service. -#[derive(Debug, Clone, Copy)] -pub struct ServiceStatusHandle(winsvc::SERVICE_STATUS_HANDLE); - -impl ServiceStatusHandle { - fn from_handle(handle: winsvc::SERVICE_STATUS_HANDLE) -> Self { - ServiceStatusHandle(handle) - } - - /// Report the new service status to the system - pub fn set_service_status(&self, service_status: ServiceStatus) -> io::Result<()> { - let mut raw_service_status = service_status.to_raw(); - let result = unsafe { winsvc::SetServiceStatus(self.0, &mut raw_service_status) }; - if result == 0 { - Err(io::Error::last_os_error()) - } else { - Ok(()) - } - } -} - -// Underlying SERVICE_STATUS_HANDLE is thread safe. -// See remarks section for more info: -// https://msdn.microsoft.com/en-us/library/windows/desktop/ms686241(v=vs.85).aspx -unsafe impl Send for ServiceStatusHandle {} - -/// Abstraction over the return value of service control handler. -/// The meaning of each of variants in this enum depends on the type of received event. -/// See the "Return value" section of corresponding MSDN article for more info: -/// https://msdn.microsoft.com/en-us/library/windows/desktop/ms683241(v=vs.85).aspx -#[derive(Debug)] -pub enum ServiceControlHandlerResult { - /// Either used to aknowledge the call or grant the permission in advanced events. - NoError, - /// The received event is not implemented. - NotImplemented, - /// This variant is used to deny permission and return the reason error code in advanced - /// events. - Other(u32), -} - -impl ServiceControlHandlerResult { - pub fn to_raw(&self) -> u32 { - match *self { - ServiceControlHandlerResult::NoError => NO_ERROR, - ServiceControlHandlerResult::NotImplemented => ERROR_CALL_NOT_IMPLEMENTED, - ServiceControlHandlerResult::Other(code) => code, - } - } -} - -/// Register a closure for receiving service events. -/// Returns `ServiceStatusHandle` that can be used to report the service status back to the system. -pub fn register_control_handler< - S: AsRef<OsStr>, - F: Fn(ServiceControl) -> ServiceControlHandlerResult + 'static, ->( - service_name: S, - event_handler: F, -) -> Result<ServiceStatusHandle> { - // Move closure data on heap. - // The Box<HandlerFn> is a trait object and is stored on stack at this point. - let heap_event_handler = Box::new(event_handler) as Box<HandlerFn>; - - // Box again to move trait object to heap. - let boxed_event_handler: Box<Box<HandlerFn>> = Box::new(heap_event_handler); - - // Important: leak the Box<Box<HandlerFn>> which will be released in `service_control_handler`. - let context = Box::into_raw(boxed_event_handler) as *mut ::std::os::raw::c_void; - - let service_name = - WideCString::from_str(service_name).chain_err(|| ErrorKind::InvalidServiceName)?; - let status_handle = unsafe { - winsvc::RegisterServiceCtrlHandlerExW( - service_name.as_ptr(), - Some(service_control_handler), - context, - ) - }; - - if status_handle.is_null() { - Err(io::Error::last_os_error().into()) - } else { - Ok(ServiceStatusHandle::from_handle(status_handle)) - } -} - -/// Alias for control event handler closure. -type HandlerFn = Fn(ServiceControl) -> ServiceControlHandlerResult; - -/// Static service control handler -#[allow(dead_code)] -extern "system" fn service_control_handler( - control: u32, - _event_type: u32, - _event_data: *mut ::std::os::raw::c_void, - context: *mut ::std::os::raw::c_void, -) -> u32 { - // Important: cast context to &mut Box<HandlerFn> without taking ownership. - let handler_fn = unsafe { &mut *(context as *mut Box<HandlerFn>) }; - - match ServiceControl::from_raw(control) { - Ok(service_control) => { - let return_code = ((handler_fn)(service_control)).to_raw(); - - // Important: release context upon Stop, Shutdown or Preshutdown at the end of the - // service lifecycle. - match service_control { - ServiceControl::Stop | ServiceControl::Shutdown | ServiceControl::Preshutdown => { - let _owned_boxed_handler: Box<Box<HandlerFn>> = - unsafe { Box::from_raw(context as *mut Box<HandlerFn>) }; - } - _ => (), - }; - - return_code - } - - // Report all unknown control commands as unimplemented - Err(_) => ServiceControlHandlerResult::NotImplemented.to_raw(), - } -} diff --git a/windows-service/src/service_dispatcher.rs b/windows-service/src/service_dispatcher.rs deleted file mode 100644 index fb9d5df8f3..0000000000 --- a/windows-service/src/service_dispatcher.rs +++ /dev/null @@ -1,90 +0,0 @@ -use std::ffi::{OsStr, OsString}; -use std::{io, ptr}; -use widestring::{WideCStr, WideCString}; -use winapi::um::winsvc; - -mod errors { - error_chain! { - errors { - InvalidServiceName { - description("Invalid service name") - } - } - foreign_links { - System(::std::io::Error); - } - } -} -pub use self::errors::*; - -/// Macro to generate a "service_main" function for Windows service. -/// -/// The `service_main` function parses service arguments provided by the system -/// and passes them with a call to `$service_main_handler`. -/// -/// `$function_name` - name of the "service_main" callback. -/// `$service_main_handler` - function with a signature `fn(Vec<OsString>)` that's called from -/// generated `$function_name`. Accepts parsed service arguments as `Vec<OsString>`. Its -/// responsibility is to create a `ServiceControlHandler`, start processing control events and -/// report the service status to the system. -/// -#[macro_export] -macro_rules! define_windows_service { - ($function_name:ident, $service_main_handler:ident) => { - /// Static callback used by the system to bootstrap the service. - /// Do not call it directly. - extern "system" fn $function_name(argc: u32, argv: *mut *mut u16) { - let arguments = unsafe { $crate::service_dispatcher::parse_raw_arguments(argc, argv) }; - - $service_main_handler(arguments); - } - }; -} - -/// Start service control dispatcher. -/// -/// Once started the service control dispatcher blocks the current thread execution -/// until the service is stopped. -/// -/// Upon successful initialization, system calls the `service_main` in -/// background thread which parses service arguments received from the system and -/// passes them to higher level `$service_main_handler` handler. -/// -/// On failure: immediately returns an error, no threads are spawned. -/// -pub fn start_dispatcher<T: AsRef<OsStr>>( - service_name: T, - service_main: extern "system" fn(u32, *mut *mut u16), -) -> Result<()> { - let service_name = - WideCString::from_str(service_name).chain_err(|| ErrorKind::InvalidServiceName)?; - let service_table: &[winsvc::SERVICE_TABLE_ENTRYW] = &[ - winsvc::SERVICE_TABLE_ENTRYW { - lpServiceName: service_name.as_ptr(), - lpServiceProc: Some(service_main), - }, - // the last item has to be { null, null } - winsvc::SERVICE_TABLE_ENTRYW { - lpServiceName: ptr::null(), - lpServiceProc: None, - }, - ]; - - let result = unsafe { winsvc::StartServiceCtrlDispatcherW(service_table.as_ptr()) }; - if result == 0 { - Err(io::Error::last_os_error().into()) - } else { - Ok(()) - } -} - -/// Parse raw arguments received from `service_main` into Vec. -pub unsafe fn parse_raw_arguments(argc: u32, argv: *mut *mut u16) -> Vec<OsString> { - (0..argc) - .into_iter() - .map(|i| { - let array_element_ptr: *mut *mut u16 = argv.offset(i as isize); - WideCStr::from_ptr_str(*array_element_ptr).to_os_string() - }) - .collect() -} diff --git a/windows-service/src/service_manager.rs b/windows-service/src/service_manager.rs deleted file mode 100644 index b4dceb9d3e..0000000000 --- a/windows-service/src/service_manager.rs +++ /dev/null @@ -1,196 +0,0 @@ -use std::borrow::Cow; -use std::ffi::OsStr; -use std::{io, ptr}; - -use widestring::{NulError, WideCString, WideString}; -use winapi::um::winsvc; - -use service::{Service, ServiceAccess, ServiceInfo}; -use shell_escape; - -mod errors { - error_chain! { - errors { - InvalidAccountName { - description("Invalid account name") - } - InvalidAccountPassword { - description("Invalid account password") - } - InvalidDisplayName { - description("Invalid display name") - } - InvalidDatabaseName { - description("Invalid database name") - } - InvalidExecutablePath { - description("Invalid executable path") - } - InvalidLaunchArgument { - description("Invalid launch argument") - } - InvalidMachineName { - description("Invalid machine name") - } - InvalidServiceName { - description("Invalid service name") - } - } - foreign_links { - System(::std::io::Error); - } - } -} -pub use self::errors::*; - -/// Flags describing access permissions for ServiceManager -bitflags! { - pub struct ServiceManagerAccess: u32 { - /// Can connect to service control manager - const CONNECT = winsvc::SC_MANAGER_CONNECT; - - /// Can create services - const CREATE_SERVICE = winsvc::SC_MANAGER_CREATE_SERVICE; - - /// Can enumerate services - const ENUMERATE_SERVICE = winsvc::SC_MANAGER_ENUMERATE_SERVICE; - } -} - -/// Service control manager -pub struct ServiceManager(winsvc::SC_HANDLE); - -impl ServiceManager { - /// Private initializer - /// Passing None for machine connects to local machine - /// Passing None for database connects to active database - fn new<M: AsRef<OsStr>, D: AsRef<OsStr>>( - machine: Option<M>, - database: Option<D>, - request_access: ServiceManagerAccess, - ) -> Result<Self> { - let machine_name = to_wide(machine).chain_err(|| ErrorKind::InvalidMachineName)?; - let database_name = to_wide(database).chain_err(|| ErrorKind::InvalidDatabaseName)?; - let handle = unsafe { - winsvc::OpenSCManagerW( - machine_name.map_or(ptr::null(), |s| s.as_ptr()), - database_name.map_or(ptr::null(), |s| s.as_ptr()), - request_access.bits(), - ) - }; - - if handle.is_null() { - Err(io::Error::last_os_error().into()) - } else { - Ok(ServiceManager(handle)) - } - } - - /// Passing None for database connects to active database - pub fn local_computer<D: AsRef<OsStr>>( - database: Option<D>, - request_access: ServiceManagerAccess, - ) -> Result<Self> { - ServiceManager::new(None::<&OsStr>, database, request_access) - } - - /// Passing None for database connects to active database - pub fn remote_computer<M: AsRef<OsStr>, D: AsRef<OsStr>>( - machine: M, - database: Option<D>, - request_access: ServiceManagerAccess, - ) -> Result<Self> { - ServiceManager::new(Some(machine), database, request_access) - } - - pub fn create_service( - &self, - service_info: ServiceInfo, - service_access: ServiceAccess, - ) -> Result<Service> { - let service_name = - WideCString::from_str(service_info.name).chain_err(|| ErrorKind::InvalidServiceName)?; - let display_name = WideCString::from_str(service_info.display_name) - .chain_err(|| ErrorKind::InvalidDisplayName)?; - let account_name = - to_wide(service_info.account_name).chain_err(|| ErrorKind::InvalidAccountName)?; - let account_password = - to_wide(service_info.account_password).chain_err(|| ErrorKind::InvalidAccountPassword)?; - - // escape executable path and arguments and combine them into single command - let executable_path = escape_wide(service_info.executable_path) - .chain_err(|| ErrorKind::InvalidExecutablePath)?; - - let mut launch_command_buffer = WideString::new(); - launch_command_buffer.push(executable_path); - - for launch_argument in service_info.launch_arguments.iter() { - let wide = escape_wide(launch_argument).chain_err(|| ErrorKind::InvalidLaunchArgument)?; - - launch_command_buffer.push_str(" "); - launch_command_buffer.push(wide); - } - - let launch_command = WideCString::from_wide_str(launch_command_buffer).unwrap(); - - let service_handle = unsafe { - winsvc::CreateServiceW( - self.0, - service_name.as_ptr(), - display_name.as_ptr(), - service_access.bits(), - service_info.service_type.to_raw(), - service_info.start_type.to_raw(), - service_info.error_control.to_raw(), - launch_command.as_ptr(), - ptr::null(), // load ordering group - ptr::null_mut(), // tag id within the load ordering group - ptr::null(), // service dependencies - account_name.map_or(ptr::null(), |s| s.as_ptr()), - account_password.map_or(ptr::null(), |s| s.as_ptr()), - ) - }; - - if service_handle.is_null() { - Err(io::Error::last_os_error().into()) - } else { - Ok(unsafe { Service::from_handle(service_handle) }) - } - } - - pub fn open_service<T: AsRef<OsStr>>( - &self, - name: T, - request_access: ServiceAccess, - ) -> Result<Service> { - let service_name = WideCString::from_str(name).chain_err(|| ErrorKind::InvalidServiceName)?; - let service_handle = - unsafe { winsvc::OpenServiceW(self.0, service_name.as_ptr(), request_access.bits()) }; - - if service_handle.is_null() { - Err(io::Error::last_os_error().into()) - } else { - Ok(unsafe { Service::from_handle(service_handle) }) - } - } -} - -impl Drop for ServiceManager { - fn drop(&mut self) { - unsafe { winsvc::CloseServiceHandle(self.0) }; - } -} - -fn to_wide<T: AsRef<OsStr>>(s: Option<T>) -> ::std::result::Result<Option<WideCString>, NulError> { - if let Some(s) = s { - Ok(Some(WideCString::from_str(s)?)) - } else { - Ok(None) - } -} - -fn escape_wide<T: AsRef<OsStr>>(s: T) -> ::std::result::Result<WideString, NulError> { - let escaped = shell_escape::escape(Cow::Borrowed(s.as_ref())); - let wide = WideCString::from_str(escaped)?; - Ok(wide.to_wide_string()) -} diff --git a/windows-service/src/shell_escape.rs b/windows-service/src/shell_escape.rs deleted file mode 100644 index 12014a3b15..0000000000 --- a/windows-service/src/shell_escape.rs +++ /dev/null @@ -1,120 +0,0 @@ -use std::borrow::Cow; -use std::ffi::{OsStr, OsString}; -use std::iter::repeat; -use std::os::windows::ffi::{OsStrExt, OsStringExt}; - -/// Common UTF-16 code points. -mod utf16 { - pub const DOUBLEQUOTE: u16 = '"' as u16; - pub const BACKSLASH: u16 = '\\' as u16; - pub const SPACE: u16 = ' ' as u16; - pub const LINEFEED: u16 = '\n' as u16; - pub const HTAB: u16 = '\t' as u16; - pub const VTAB: u16 = 0x000B; // '\v' -} - -/// Loselessly escape shell arguments on Windows. -/// -/// Inspired by https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/. -/// Heavily based on https://github.com/sfackler/shell-escape -pub fn escape(s: Cow<OsStr>) -> Cow<OsStr> { - static ESCAPE_CHARS: &'static [u16] = &[ - utf16::DOUBLEQUOTE, - utf16::SPACE, - utf16::LINEFEED, - utf16::HTAB, - utf16::VTAB, - ]; - let needs_escape = s.is_empty() || s.encode_wide().any(|ref c| ESCAPE_CHARS.contains(c)); - if !needs_escape { - return s; - } - - let mut escaped_wide_string: Vec<u16> = Vec::with_capacity(s.len() + 2); - escaped_wide_string.push(utf16::DOUBLEQUOTE); - - let mut chars = s.encode_wide().peekable(); - loop { - let mut num_slashes = 0; - while let Some(&utf16::BACKSLASH) = chars.peek() { - chars.next(); - num_slashes += 1; - } - - match chars.next() { - Some(utf16::DOUBLEQUOTE) => { - escaped_wide_string.extend(repeat(utf16::BACKSLASH).take(num_slashes * 2 + 1)); - escaped_wide_string.push(utf16::DOUBLEQUOTE); - } - Some(c) => { - escaped_wide_string.extend(repeat(utf16::BACKSLASH).take(num_slashes)); - escaped_wide_string.push(c); - } - None => { - escaped_wide_string.extend(repeat(utf16::BACKSLASH).take(num_slashes * 2)); - break; - } - } - } - - escaped_wide_string.push(utf16::DOUBLEQUOTE); - - Cow::Owned(OsString::from_wide(&escaped_wide_string)) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_no_escape() { - assert_eq!( - escape(Cow::Borrowed(OsStr::new("--aaa=bbb-ccc"))), - OsStr::new("--aaa=bbb-ccc") - ); - } - - #[test] - fn test_escape_empty_argument() { - assert_eq!(escape(Cow::Borrowed(OsStr::new(""))), OsStr::new(r#""""#)); - } - - #[test] - fn test_escape_argument_with_spaces() { - assert_eq!( - escape(Cow::Borrowed(OsStr::new("linker=gcc -L/foo -Wl,bar"))), - OsStr::new(r#""linker=gcc -L/foo -Wl,bar""#) - ); - } - - #[test] - fn test_escape_nested_quotes() { - assert_eq!( - escape(Cow::Borrowed(OsStr::new(r#"--features="default""#))), - OsStr::new(r#""--features=\"default\"""#) - ); - } - - - #[test] - fn test_escape_multiple_backslashes_and_nested_quotes() { - assert_eq!( - escape(Cow::Borrowed(OsStr::new(r#"hello \\\"quote\\\""#))), - OsStr::new(r#""hello \\\\\\\"quote\\\\\\\"""#) - ); - } - - // Input: - // child.exe "\some\directory with\spaces\" argument2 - // - // Parsed as: - // 0: [child.exe] - // 1: [\some\directory with\spaces" argument2] - #[test] - fn test_escape_trailing_backslash() { - assert_eq!( - escape(Cow::Borrowed(OsStr::new(r#"\some\directory with\spaces\"#))), - OsStr::new(r#""\some\directory with\spaces\\""#) - ); - } -} |
