pispas_service\service/
pispas_service.rs

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