use std::ffi::{OsStr, OsString};
use std::path::Path;
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
use anyhow::Context;
use derive_more::From;
use log::*;
use windows_service::service::{
ServiceControl, ServiceControlAccept, ServiceExitCode, ServiceState, ServiceStatus, ServiceType,
};
use windows_service::service_control_handler::{self, ServiceControlHandlerResult};
use windows_service::{define_windows_service, service_dispatcher};
use super::Cli;
const SERVICE_NAME: &str = "distant_manager";
const SERVICE_TYPE: ServiceType = ServiceType::OWN_PROCESS;
#[derive(serde::Serialize, serde::Deserialize)]
struct Config {
pub args: Vec<std::ffi::OsString>,
}
impl Config {
pub fn save(&self) -> anyhow::Result<()> {
let mut bytes = Vec::new();
serde_json::to_writer(&mut bytes, self).context("Could not convert config into json")?;
std::fs::write(Self::config_file(), bytes).context("Could not write config to file")
}
pub fn load() -> anyhow::Result<Self> {
let bytes = std::fs::read(Self::config_file()).context("Could not read config file")?;
serde_json::from_slice(&bytes).context("Could not convert json into config")
}
pub fn delete() -> anyhow::Result<()> {
std::fs::remove_file(Self::config_file()).context("Could not delete config file")
}
fn config_file() -> std::path::PathBuf {
let mut path = std::env::current_exe().unwrap();
path.set_extension("exe.config");
path
}
}
#[derive(From)]
pub enum ServiceError {
Anyhow(anyhow::Error),
Service(windows_service::Error),
}
pub fn run() -> Result<(), ServiceError> {
let config = Config {
args: std::env::args_os().collect(),
};
config.save()?;
let result = service_dispatcher::start(SERVICE_NAME, ffi_service_main);
let config_result = Config::delete();
match (result, config_result) {
(Ok(_), Ok(_)) => Ok(()),
(Err(x), _) => Err(ServiceError::Service(x)),
(_, Err(x)) => Err(ServiceError::Anyhow(x)),
}
}
pub fn is_windows_service() -> bool {
use sysinfo::{Pid, PidExt, Process, ProcessExt, System, SystemExt};
let mut system = System::new();
let pid = Pid::from_u32(std::process::id());
system.refresh_process(pid);
let maybe_parent_pid = system.process(pid).and_then(Process::parent);
if let Some(pid) = maybe_parent_pid {
system.refresh_process(pid);
}
maybe_parent_pid
.and_then(|pid| system.process(pid))
.map(Process::exe)
.and_then(Path::file_name)
.map(OsStr::to_string_lossy)
.map(|s| s.eq_ignore_ascii_case("services"))
.unwrap_or_default()
}
define_windows_service!(ffi_service_main, service_main);
fn service_main(_arguments: Vec<OsString>) {
if let Err(_e) = run_service() {
}
}
fn run_service() -> windows_service::Result<()> {
debug!("Starting windows service for {SERVICE_NAME}");
let (shutdown_tx, shutdown_rx) = std::sync::mpsc::channel();
let event_handler = {
move |control_event| -> ServiceControlHandlerResult {
match control_event {
ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
ServiceControl::Stop => {
shutdown_tx.send(true).unwrap();
ServiceControlHandlerResult::NoError
}
_ => ServiceControlHandlerResult::NotImplemented,
}
}
};
debug!("Registering service control handler for {SERVICE_NAME}");
let status_handle = service_control_handler::register(SERVICE_NAME, event_handler)?;
debug!("Setting service status as running for {SERVICE_NAME}");
status_handle.set_service_status(ServiceStatus {
service_type: SERVICE_TYPE,
current_state: ServiceState::Running,
controls_accepted: ServiceControlAccept::STOP,
exit_code: ServiceExitCode::Win32(0),
checkpoint: 0,
wait_hint: Duration::default(),
process_id: None,
})?;
debug!("Spawning CLI thread for {SERVICE_NAME}");
let handle = thread::spawn({
move || {
debug!("Loading CLI using args from disk for {SERVICE_NAME}");
let config = Config::load().expect("Failed to load config");
debug!("Parsing CLI args from disk for {SERVICE_NAME}");
let cli = Cli::initialize_from(config.args).expect("Failed to initialize CLI");
debug!("Running CLI for {SERVICE_NAME}");
cli.run().expect("CLI failed during execution")
}
});
let success = loop {
if handle.is_finished() {
match handle.join() {
Ok(_) => break true,
Err(x) => {
error!("{x:?}");
break false;
}
}
}
match shutdown_rx.try_recv() {
Ok(_) | Err(mpsc::TryRecvError::Disconnected) => break true,
Err(mpsc::TryRecvError::Empty) => thread::sleep(Duration::from_millis(100)),
}
};
debug!("Setting service status as stopped for {SERVICE_NAME}");
status_handle.set_service_status(ServiceStatus {
service_type: SERVICE_TYPE,
current_state: ServiceState::Stopped,
controls_accepted: ServiceControlAccept::empty(),
exit_code: if success {
ServiceExitCode::NO_ERROR
} else {
ServiceExitCode::ServiceSpecific(1u32)
},
checkpoint: 0,
wait_hint: Duration::default(),
process_id: None,
})?;
Ok(())
}