pispas_service\service/
pispas_service.rs

1use parking_lot::RwLock;
2use std::{
3    sync::Arc,
4    sync::atomic::{AtomicBool, Ordering},
5};
6#[cfg(windows)]
7use std::thread::sleep;
8#[cfg(windows)]
9use std::time::Duration;
10use easy_trace::instruments::tracing::{info};
11
12// Windows-specific imports
13#[cfg(windows)]
14use {
15    std::ffi::OsString,
16    windows_service::{
17        define_windows_service,
18        service::{
19            ServiceControl, ServiceControlAccept, ServiceExitCode, ServiceState, ServiceStatus,
20            ServiceType,
21        },
22        service_control_handler::{self, ServiceControlHandlerResult},
23        service_dispatcher, Result as WindowsResult,
24    },
25};
26
27// Unix-specific imports
28#[cfg(unix)]
29use {
30    signal_hook::{consts::SIGTERM, consts::SIGINT, consts::SIGHUP, iterator::Signals},
31    std::thread,
32};
33#[cfg(windows)]
34use easy_trace::instruments::tracing;
35#[cfg(windows)]
36use easy_trace::instruments::tracing::error;
37
38#[cfg(windows)]
39const SERVICE_TYPE: ServiceType = ServiceType::OWN_PROCESS;
40
41// Common error type for cross-platform compatibility
42#[derive(Debug)]
43pub enum ServiceError {
44    #[cfg(windows)]
45    Windows(windows_service::Error),
46    #[cfg(unix)]
47    Unix(std::io::Error),
48    _General(String),
49}
50
51impl std::fmt::Display for ServiceError {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        match self {
54            #[cfg(windows)]
55            ServiceError::Windows(e) => write!(f, "Windows service error: {}", e),
56            #[cfg(unix)]
57            ServiceError::Unix(e) => write!(f, "Unix daemon error: {}", e),
58            ServiceError::_General(e) => write!(f, "Service error: {}", e),
59        }
60    }
61}
62
63impl std::error::Error for ServiceError {}
64
65#[cfg(windows)]
66impl From<windows_service::Error> for ServiceError {
67    fn from(error: windows_service::Error) -> Self {
68        ServiceError::Windows(error)
69    }
70}
71
72#[cfg(unix)]
73impl From<std::io::Error> for ServiceError {
74    fn from(error: std::io::Error) -> Self {
75        ServiceError::Unix(error)
76    }
77}
78
79// Windows implementation
80#[cfg(windows)]
81pub fn run() -> Result<(), ServiceError> {
82    service_dispatcher::start(sharing::SERVICE_NAME, ffi_service_main)
83        .map_err(ServiceError::from)
84}
85
86// Unix implementation (Linux/macOS)
87#[cfg(unix)]
88pub fn run() -> Result<(), ServiceError> {
89    info!("Starting Unix daemon");
90    run_daemon()
91}
92
93#[cfg(windows)]
94define_windows_service!(ffi_service_main, my_service_main);
95
96#[cfg(windows)]
97pub fn my_service_main(_arguments: Vec<OsString>) {
98    info!("Call my_service_main with {:?}", _arguments);
99    if let Err(e) = run_service() {
100        tracing::error!("Error starting service: {}", e);
101    }
102}
103
104// Unix daemon implementation
105#[cfg(unix)]
106pub fn run_daemon() -> Result<(), ServiceError> {
107    let cancel_token = Arc::new(AtomicBool::new(false));
108    let signal_cancel = cancel_token.clone();
109
110    // Setup signal handling for graceful shutdown
111    let mut signals = Signals::new(&[SIGTERM, SIGINT, SIGHUP])
112        .map_err(ServiceError::from)?;
113
114    thread::spawn(move || {
115        for sig in signals.forever() {
116            info!("Received signal: {}", sig);
117            match sig {
118                SIGTERM | SIGINT => {
119                    info!("Received termination signal, shutting down gracefully");
120                    signal_cancel.store(true, Ordering::Relaxed);
121                    break;
122                }
123                SIGHUP => {
124                    info!("Received SIGHUP, could implement config reload here");
125                    // You could implement configuration reload here
126                }
127                _ => {}
128            }
129        }
130    });
131    println!("Running service in Unix mode");
132    run_service_common(cancel_token)
133}
134
135#[cfg(windows)]
136pub fn run_service() -> WindowsResult<()> {
137    let cancel_token = Arc::new(AtomicBool::new(false));
138    let thread_cancel = cancel_token.clone();
139
140    // Define system service event handler
141    let event_handler = move |control_event| -> ServiceControlHandlerResult {
142        match control_event {
143            ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
144            ServiceControl::Stop => {
145                info!("Received stop signal");
146                thread_cancel.store(true, Ordering::Relaxed);
147                sleep(Duration::from_secs(2));
148                ServiceControlHandlerResult::NoError
149            }
150            _ => ServiceControlHandlerResult::NotImplemented,
151        }
152    };
153
154    let status_handle = service_control_handler::register(sharing::SERVICE_NAME, event_handler)?;
155
156    // Tell the system that service is running
157    status_handle.set_service_status(ServiceStatus {
158        service_type: SERVICE_TYPE,
159        current_state: ServiceState::Running,
160        controls_accepted: ServiceControlAccept::STOP,
161        exit_code: ServiceExitCode::Win32(0),
162        checkpoint: 0,
163        wait_hint: Duration::default(),
164        process_id: None,
165    })?;
166
167    // Run the common service logic
168    if let Err(e) = run_service_common(cancel_token) {
169        error!("Service error: {}", e);
170    }
171
172    // Tell the system that service has stopped
173    status_handle.set_service_status(ServiceStatus {
174        service_type: SERVICE_TYPE,
175        current_state: ServiceState::Stopped,
176        controls_accepted: ServiceControlAccept::empty(),
177        exit_code: ServiceExitCode::Win32(0),
178        checkpoint: 0,
179        wait_hint: Duration::default(),
180        process_id: None,
181    })?;
182
183    Ok(())
184}
185
186// Common service logic for both platforms
187fn run_service_common(cancel_token: Arc<AtomicBool>) -> Result<(), ServiceError> {
188    info!("Starting service");
189
190    let server = Arc::new(RwLock::new(crate::service::server::Server::new()));
191
192    // Use the cancel_token directly (assuming CancelToken = Arc<AtomicBool>)
193    let service_cancel_token = cancel_token.clone();
194
195    info!("Starting service controller");
196
197    // Run the server - this should be the main service logic
198    crate::service::server::Server::run_server(server.clone(), service_cancel_token);
199
200    info!("Service has been stopped");
201    Ok(())
202}
203
204// Stub for non-Unix, non-Windows platforms (if any)
205#[cfg(not(any(windows, unix)))]
206pub fn run() -> Result<(), ServiceError> {
207    Err(ServiceError::General("Platform not supported".to_string()))
208}
209
210// Helper function for direct execution (non-service mode)
211pub fn run_direct() -> Result<(), ServiceError> {
212    info!("Running in direct mode");
213    let cancel_token = Arc::new(AtomicBool::new(false));
214
215    #[cfg(unix)]
216    {
217        // Setup signal handling for direct mode too
218        let signal_cancel = cancel_token.clone();
219        let mut signals = Signals::new(&[SIGTERM, SIGINT])
220            .map_err(ServiceError::from)?;
221
222        thread::spawn(move || {
223            for sig in signals.forever() {
224                info!("Received signal: {}, shutting down", sig);
225                signal_cancel.store(true, Ordering::Relaxed);
226                break;
227            }
228        });
229    }
230
231    run_service_common(cancel_token)
232}