Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
2 changes: 1 addition & 1 deletion src/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pub type ServiceMainFn<T> = fn(
tx: mpsc::Sender<ServiceEvent<T>>,
args: Vec<String>,
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.
Expand Down
2 changes: 1 addition & 1 deletion src/controller/dummy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::process::ExitCode, Error> {
unimplemented!();
}
}
Expand Down
18 changes: 10 additions & 8 deletions src/controller/linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use {
systemd_rs::login::session as login_session,
};

type LinuxServiceMainWrapperFn = extern "system" fn(args: Vec<String>);
type LinuxServiceMainWrapperFn = fn(args: Vec<String>) -> u8;
pub type Session = session::Session_<String>;

fn systemctl_execute(args: &[&str]) -> Result<(), Error> {
Expand Down Expand Up @@ -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<std::process::ExitCode, Error> {
Ok(std::process::ExitCode::from(service_main_wrapper(env::args().collect())))
}

fn get_service_file_name(&self) -> String {
Expand Down Expand Up @@ -306,14 +305,17 @@ fn run_monitor<T: Send + 'static>(tx: mpsc::Sender<ServiceEvent<T>>) -> Result<M
#[macro_export]
macro_rules! Service {
($name:expr, $function:ident) => {
extern "system" fn service_main_wrapper(args: Vec<String>) {
dispatch($function, args);
fn service_main_wrapper(args: Vec<String>) -> u8 {
dispatch($function, args)
}
};
}

#[doc(hidden)]
pub fn dispatch<T: Send + 'static>(service_main: ServiceMainFn<T>, args: Vec<String>) {
pub fn dispatch<T: Send + 'static>(
service_main: ServiceMainFn<T>,
args: Vec<String>,
) -> u8 {
let (tx, rx) = mpsc::channel();

#[cfg(feature = "systemd-rs")]
Expand All @@ -327,5 +329,5 @@ pub fn dispatch<T: Send + 'static>(service_main: ServiceMainFn<T>, args: Vec<Str
let _ = tx.send(ServiceEvent::Stop);
})
.expect("Failed to register Ctrl-C handler");
service_main(rx, _tx, args, false);
service_main(rx, _tx, args, false)
}
17 changes: 9 additions & 8 deletions src/controller/macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ use crate::session;
use crate::Error;
use crate::ServiceEvent;

type MacosServiceMainWrapperFn = extern "system" fn(args: Vec<String>);
type MacosServiceMainWrapperFn = fn(args: Vec<String>) -> u8;
pub type Session = session::Session_<u32>;

pub enum LaunchAgentTargetSesssion {
Expand Down Expand Up @@ -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<std::process::ExitCode, Error> {
Ok(std::process::ExitCode::from(service_main_wrapper(env::args().collect())))
}

fn get_plist_content(&self) -> Result<String, Error> {
Expand Down Expand Up @@ -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<String>) {
dispatch($function, args);
fn service_main_wrapper(args: Vec<String>) -> u8 {
dispatch($function, args)
}
};
}
Expand Down Expand Up @@ -485,7 +484,7 @@ pub fn run_monitor<T: Send + 'static>(
}

#[doc(hidden)]
pub fn dispatch<T: Send + 'static>(service_main: ServiceMainFn<T>, args: Vec<String>) {
pub fn dispatch<T: Send + 'static>(service_main: ServiceMainFn<T>, args: Vec<String>) -> u8 {
let (tx, rx) = mpsc::channel();

let mut session_monitor = run_monitor(tx.clone()).expect("Failed to run session monitor");
Expand All @@ -495,7 +494,9 @@ pub fn dispatch<T: Send + 'static>(service_main: ServiceMainFn<T>, args: Vec<Str
let _ = tx.send(ServiceEvent::Stop);
})
.expect("Failed to register Ctrl-C handler");
service_main(rx, _tx, args, false);
let exit_code = service_main(rx, _tx, args, false);

session_monitor.stop();

exit_code
}
26 changes: 16 additions & 10 deletions src/controller/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::{thread, time};
use widestring::WideCString;
use windows_sys::core::PWSTR;
use windows_sys::Win32::{
Foundation::{GetLastError, ERROR_CALL_NOT_IMPLEMENTED, MAX_PATH},
Foundation::{GetLastError, ERROR_CALL_NOT_IMPLEMENTED, ERROR_SERVICE_SPECIFIC_ERROR, MAX_PATH},
Security::SC_HANDLE,
System::{
Diagnostics::Debug::{FormatMessageW, FORMAT_MESSAGE_FROM_SYSTEM},
Expand Down Expand Up @@ -274,7 +274,7 @@ impl WindowsController {
pub fn register(
&mut self,
service_main_wrapper: WindowsServiceMainWrapperFn,
) -> Result<(), Error> {
) -> Result<std::process::ExitCode, Error> {
unsafe {
let mut service_name = get_utf16(self.service_name.as_str());

Expand All @@ -288,7 +288,7 @@ impl WindowsController {

match StartServiceCtrlDispatcherW(*service_table.as_ptr()) {
0 => Err(Error::new("StartServiceCtrlDispatcher")),
_ => Ok(()),
_ => Ok(std::process::ExitCode::SUCCESS),
}
Comment on lines 289 to 292
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WindowsController::register now returns ExitCode, but it always returns ExitCode::SUCCESS on success from StartServiceCtrlDispatcherW, discarding the service's actual exit code. If the goal is to propagate the service exit code to the caller, consider capturing the exit_code produced in dispatch (e.g., via a static atomic or other shared state) and returning it once StartServiceCtrlDispatcherW unblocks; otherwise, this API should be documented as always returning SUCCESS on Windows to avoid surprising callers.

Copilot uses AI. Check for mistakes.
}
}
Expand All @@ -298,16 +298,22 @@ 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,
dwControlsAccepted: SERVICE_ACCEPT_STOP
| SERVICE_ACCEPT_SHUTDOWN
| SERVICE_ACCEPT_PAUSE_CONTINUE
| SERVICE_ACCEPT_SESSIONCHANGE,
dwWin32ExitCode: 0,
dwServiceSpecificExitCode: 0,
dwWin32ExitCode: win32_exit_code,
dwServiceSpecificExitCode: service_specific_exit_code,
Comment on lines 312 to +316
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dwControlsAccepted is currently set to the same flags regardless of current_state. Per the Windows service API contract, dwControlsAccepted should be 0 in SERVICE_START_PENDING/SERVICE_STOP_PENDING and SERVICE_STOPPED states. Consider setting it conditionally based on current_state to avoid reporting unsupported controls to the SCM.

Copilot uses AI. Check for mistakes.
dwCheckPoint: 0,
dwWaitHint: wait_hint,
};
Expand All @@ -326,7 +332,7 @@ unsafe extern "system" fn service_handler<T>(

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
}
Expand Down Expand Up @@ -444,8 +450,8 @@ pub fn dispatch<T>(service_main: ServiceMainFn<T>, 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));
}
Comment on lines +453 to 457
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description says dispatch (and the Service! wrapper) return a u8 exit code, but on Windows dispatch still returns () and the Service!-generated service_main_wrapper cannot return an exit code due to the Windows service ABI. Either update the Windows API to expose the exit code to the caller (e.g., make dispatch return u8 and/or stash it for register to return), or clarify the PR description/docs that Windows is an exception and the exit code is only reported to the SCM.

Copilot uses AI. Check for mistakes.
Loading