From 956430cc6de2132f82864a36a2fd735b61f7e664 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20CORTIER?= Date: Sat, 18 Apr 2026 00:20:16 +0900 Subject: [PATCH] fix: propagate service exit code to the caller ServiceMainFn now returns u8, matching POSIX's 0-255 exit code range. dispatch and the Service! macro wrapper return u8 accordingly. register returns Result on all platforms. On Linux/macOS, the caller receives the exit code and is responsible for propagating it to the OS. On Windows, dispatch reports the exit code to the SCM via SetServiceStatus with dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR and dwServiceSpecificExitCode = u32::from(exit_code). --- Cargo.toml | 2 +- src/controller.rs | 2 +- src/controller/dummy.rs | 2 +- src/controller/linux.rs | 18 ++++++++++-------- src/controller/macos.rs | 17 +++++++++-------- src/controller/windows.rs | 26 ++++++++++++++++---------- 6 files changed, 38 insertions(+), 29 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ce2d068..42d5a5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ceviche" -version = "0.7.0" +version = "0.8.0" edition = "2021" license = "MIT/Apache-2.0" homepage = "https://github.com/devolutions/ceviche-rs" diff --git a/src/controller.rs b/src/controller.rs index 1e463d4..750e023 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -36,7 +36,7 @@ pub type ServiceMainFn = fn( tx: mpsc::Sender>, args: Vec, standalone_mode: bool, -) -> u32; +) -> u8; /// Controllers implement this interface. They also need to implement the `register()` method; because the signature /// of service_main_wrapper depends on the system the method is not part of the interface. diff --git a/src/controller/dummy.rs b/src/controller/dummy.rs index 8cfcdf8..30b045d 100644 --- a/src/controller/dummy.rs +++ b/src/controller/dummy.rs @@ -35,7 +35,7 @@ impl DummyController { DummyController {} } - pub fn register(&mut self, _service_main_wrapper: fn()) -> Result<(), Error> { + pub fn register(&mut self, _service_main_wrapper: fn()) -> Result { unimplemented!(); } } diff --git a/src/controller/linux.rs b/src/controller/linux.rs index c3c4113..7a6cf22 100644 --- a/src/controller/linux.rs +++ b/src/controller/linux.rs @@ -19,7 +19,7 @@ use { systemd_rs::login::session as login_session, }; -type LinuxServiceMainWrapperFn = extern "system" fn(args: Vec); +type LinuxServiceMainWrapperFn = fn(args: Vec) -> u8; pub type Session = session::Session_; fn systemctl_execute(args: &[&str]) -> Result<(), Error> { @@ -158,9 +158,8 @@ impl LinuxController { } } - pub fn register(&mut self, service_main_wrapper: LinuxServiceMainWrapperFn) -> Result<(), Error> { - service_main_wrapper(env::args().collect()); - Ok(()) + pub fn register(&mut self, service_main_wrapper: LinuxServiceMainWrapperFn) -> Result { + Ok(std::process::ExitCode::from(service_main_wrapper(env::args().collect()))) } fn get_service_file_name(&self) -> String { @@ -306,14 +305,17 @@ fn run_monitor(tx: mpsc::Sender>) -> Result { - extern "system" fn service_main_wrapper(args: Vec) { - dispatch($function, args); + fn service_main_wrapper(args: Vec) -> u8 { + dispatch($function, args) } }; } #[doc(hidden)] -pub fn dispatch(service_main: ServiceMainFn, args: Vec) { +pub fn dispatch( + service_main: ServiceMainFn, + args: Vec, +) -> u8 { let (tx, rx) = mpsc::channel(); #[cfg(feature = "systemd-rs")] @@ -327,5 +329,5 @@ pub fn dispatch(service_main: ServiceMainFn, args: Vec); +type MacosServiceMainWrapperFn = fn(args: Vec) -> u8; pub type Session = session::Session_; pub enum LaunchAgentTargetSesssion { @@ -149,9 +149,8 @@ impl MacosController { pub fn register( &mut self, service_main_wrapper: MacosServiceMainWrapperFn, - ) -> Result<(), Error> { - service_main_wrapper(env::args().collect()); - Ok(()) + ) -> Result { + Ok(std::process::ExitCode::from(service_main_wrapper(env::args().collect()))) } fn get_plist_content(&self) -> Result { @@ -292,8 +291,8 @@ impl ControllerInterface for MacosController { #[macro_export] macro_rules! Service { ($name:expr, $function:ident) => { - extern "system" fn service_main_wrapper(args: Vec) { - dispatch($function, args); + fn service_main_wrapper(args: Vec) -> u8 { + dispatch($function, args) } }; } @@ -485,7 +484,7 @@ pub fn run_monitor( } #[doc(hidden)] -pub fn dispatch(service_main: ServiceMainFn, args: Vec) { +pub fn dispatch(service_main: ServiceMainFn, args: Vec) -> u8 { let (tx, rx) = mpsc::channel(); let mut session_monitor = run_monitor(tx.clone()).expect("Failed to run session monitor"); @@ -495,7 +494,9 @@ pub fn dispatch(service_main: ServiceMainFn, args: Vec Result<(), Error> { + ) -> Result { unsafe { let mut service_name = get_utf16(self.service_name.as_str()); @@ -288,7 +288,7 @@ impl WindowsController { match StartServiceCtrlDispatcherW(*service_table.as_ptr()) { 0 => Err(Error::new("StartServiceCtrlDispatcher")), - _ => Ok(()), + _ => Ok(std::process::ExitCode::SUCCESS), } } } @@ -298,7 +298,13 @@ fn set_service_status( status_handle: SERVICE_STATUS_HANDLE, current_state: DWORD, wait_hint: DWORD, + service_exit_code: DWORD, ) { + let (win32_exit_code, service_specific_exit_code) = if service_exit_code != 0 { + (ERROR_SERVICE_SPECIFIC_ERROR, service_exit_code) + } else { + (0, 0) + }; let mut service_status = SERVICE_STATUS { dwServiceType: SERVICE_WIN32_OWN_PROCESS, dwCurrentState: current_state, @@ -306,8 +312,8 @@ fn set_service_status( | SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_PAUSE_CONTINUE | SERVICE_ACCEPT_SESSIONCHANGE, - dwWin32ExitCode: 0, - dwServiceSpecificExitCode: 0, + dwWin32ExitCode: win32_exit_code, + dwServiceSpecificExitCode: service_specific_exit_code, dwCheckPoint: 0, dwWaitHint: wait_hint, }; @@ -326,7 +332,7 @@ unsafe extern "system" fn service_handler( match control { SERVICE_CONTROL_STOP | SERVICE_CONTROL_SHUTDOWN => { - set_service_status(SERVICE_CONTROL_HANDLE, SERVICE_STOP_PENDING, 10); + set_service_status(SERVICE_CONTROL_HANDLE, SERVICE_STOP_PENDING, 10, 0); let _ = (*tx).send(ServiceEvent::Stop); 0 } @@ -444,8 +450,8 @@ pub fn dispatch(service_main: ServiceMainFn, name: &str, argc: DWORD, argv ) }; unsafe { SERVICE_CONTROL_HANDLE = ctrl_handle }; - set_service_status(ctrl_handle, SERVICE_START_PENDING, 0); - set_service_status(ctrl_handle, SERVICE_RUNNING, 0); - service_main(rx, _tx, args, false); - set_service_status(ctrl_handle, SERVICE_STOPPED, 0); + set_service_status(ctrl_handle, SERVICE_START_PENDING, 0, 0); + set_service_status(ctrl_handle, SERVICE_RUNNING, 0, 0); + let exit_code = service_main(rx, _tx, args, false); + set_service_status(ctrl_handle, SERVICE_STOPPED, 0, u32::from(exit_code)); }