use std::ffi::OsString;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use anyhow::{Context, Result};
use tracing::{error, info, warn};
use windows_service::service::{
ServiceControl, ServiceControlAccept, ServiceExitCode, ServiceState, ServiceStatus, ServiceType,
};
use windows_service::service_control_handler::{self, ServiceControlHandlerResult};
use windows_service::service_dispatcher;
const SERVICE_NAME: &str = "ZLayerDaemon-Overlayd";
struct ServiceArgs {
data_dir: PathBuf,
socket: PathBuf,
}
static SERVICE_ARGS: Mutex<Option<ServiceArgs>> = Mutex::new(None);
pub fn run_as_overlayd_service(data_dir: PathBuf, socket: PathBuf) -> Result<()> {
{
let mut slot = SERVICE_ARGS
.lock()
.expect("SERVICE_ARGS mutex poisoned (overlayd service startup)");
*slot = Some(ServiceArgs { data_dir, socket });
}
info!(service = %SERVICE_NAME, "Starting overlayd Windows Service dispatcher");
service_dispatcher::start(SERVICE_NAME, ffi_service_main).with_context(|| {
format!(
"Failed to start Windows Service dispatcher for '{SERVICE_NAME}'. \
This binary must be launched by the Service Control Manager; \
run `zlayer daemon install` then `sc start {SERVICE_NAME}` instead \
of invoking `zlayer-overlayd --service` directly."
)
})?;
Ok(())
}
windows_service::define_windows_service!(ffi_service_main, my_service_main);
fn my_service_main(_args: Vec<OsString>) {
if let Err(e) = run_service() {
error!(error = %e, "overlayd Windows Service exited with error");
}
}
fn run_service() -> Result<()> {
let ServiceArgs { data_dir, socket } = SERVICE_ARGS
.lock()
.expect("SERVICE_ARGS mutex poisoned (overlayd service main)")
.take()
.context(
"overlayd Windows Service entry point invoked without stashed arguments. \
This is a BUG: `run_as_overlayd_service` must populate SERVICE_ARGS \
before calling `service_dispatcher::start`.",
)?;
let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel::<()>();
let shutdown_tx = Arc::new(Mutex::new(Some(shutdown_tx)));
let handler_tx = Arc::clone(&shutdown_tx);
let event_handler = move |control_event| -> ServiceControlHandlerResult {
match control_event {
ServiceControl::Stop | ServiceControl::Shutdown => {
info!(?control_event, "SCM control received, signaling shutdown");
if let Some(tx) = handler_tx
.lock()
.expect("overlayd shutdown_tx mutex poisoned")
.take()
{
let _ = tx.send(());
}
ServiceControlHandlerResult::NoError
}
ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
_ => ServiceControlHandlerResult::NotImplemented,
}
};
let status_handle = service_control_handler::register(SERVICE_NAME, event_handler)
.context("Failed to register SCM control handler")?;
status_handle
.set_service_status(ServiceStatus {
service_type: ServiceType::OWN_PROCESS,
current_state: ServiceState::Running,
controls_accepted: ServiceControlAccept::STOP | ServiceControlAccept::SHUTDOWN,
exit_code: ServiceExitCode::Win32(0),
checkpoint: 0,
wait_hint: Duration::default(),
process_id: None,
})
.context("Failed to report ServiceState::Running to SCM")?;
info!(
service = %SERVICE_NAME,
"overlayd Windows Service reported Running to SCM, starting daemon"
);
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.thread_name("zlayer-overlayd-service-worker")
.build()
.context("Failed to create tokio runtime for overlayd Windows Service")?;
let serve_result = runtime.block_on(crate::run_overlayd(data_dir, socket, shutdown_rx));
let (exit_code, clean) = match &serve_result {
Ok(()) => {
info!("overlayd Windows Service daemon exited cleanly");
(ServiceExitCode::Win32(0), true)
}
Err(e) => {
warn!(error = %e, "overlayd Windows Service daemon exited with error");
(ServiceExitCode::Win32(1), false)
}
};
status_handle
.set_service_status(ServiceStatus {
service_type: ServiceType::OWN_PROCESS,
current_state: ServiceState::Stopped,
controls_accepted: ServiceControlAccept::empty(),
exit_code,
checkpoint: 0,
wait_hint: Duration::default(),
process_id: None,
})
.context("Failed to report ServiceState::Stopped to SCM")?;
info!(clean, "overlayd Windows Service reported Stopped to SCM");
serve_result
}