pipette/
winsvc.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Code to run pipette as a Windows service.
5
6use crate::agent::Agent;
7use anyhow::Context;
8use futures_concurrency::future::Race;
9use pal_async::DefaultDriver;
10use pal_async::DefaultPool;
11use std::ffi::OsString;
12use std::time::Duration;
13use windows_service::define_windows_service;
14use windows_service::service;
15use windows_service::service_control_handler;
16use windows_service::service_control_handler::ServiceControlHandlerResult;
17use windows_service::service_dispatcher;
18
19const SERVICE_NAME: &str = "pipette";
20
21pub fn start_service() -> anyhow::Result<()> {
22    // TODO: retarget stderr somewhere that the host can see (serial port?)
23    define_windows_service!(ffi_service_main, service_main);
24    service_dispatcher::start(SERVICE_NAME, ffi_service_main).context("failed to start service")?;
25    Ok(())
26}
27
28fn service_main(_args: Vec<OsString>) {
29    DefaultPool::run_with(async |driver| {
30        if let Err(e) = service_main_inner(driver).await {
31            eprintln!("service_main failed: {:#}", e);
32        }
33    })
34}
35
36async fn service_main_inner(driver: DefaultDriver) -> anyhow::Result<()> {
37    let (send, recv) = mesh::oneshot::<()>();
38    let mut send = Some(send);
39    let event_handler = move |control_event| match control_event {
40        service::ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
41        service::ServiceControl::Stop => {
42            let _ = send.take();
43            ServiceControlHandlerResult::NoError
44        }
45        _ => ServiceControlHandlerResult::NotImplemented,
46    };
47
48    let status_handle = service_control_handler::register(SERVICE_NAME, event_handler)?;
49    let set_status = |current_state| {
50        status_handle
51            .set_service_status(service::ServiceStatus {
52                service_type: service::ServiceType::OWN_PROCESS,
53                current_state,
54                controls_accepted: service::ServiceControlAccept::STOP,
55                exit_code: service::ServiceExitCode::Win32(0),
56                checkpoint: 0,
57                wait_hint: Duration::ZERO,
58                process_id: None,
59            })
60            .context("failed to set service status")
61    };
62
63    set_status(service::ServiceState::StartPending)?;
64
65    let run = async {
66        let agent = Agent::new(driver).await?;
67        set_status(service::ServiceState::Running)?;
68        agent.run().await
69    };
70
71    let stop = async {
72        recv.await.ok();
73        set_status(service::ServiceState::StopPending)
74    };
75
76    let r = (run, stop).race().await;
77
78    let exit_code = match r {
79        Ok(()) => 0,
80        Err(_) => 1,
81    };
82
83    status_handle
84        .set_service_status(service::ServiceStatus {
85            service_type: service::ServiceType::OWN_PROCESS,
86            current_state: service::ServiceState::Stopped,
87            controls_accepted: service::ServiceControlAccept::STOP,
88            exit_code: service::ServiceExitCode::Win32(exit_code),
89            checkpoint: 0,
90            wait_hint: Duration::ZERO,
91            process_id: None,
92        })
93        .ok();
94
95    r
96}