mirror of
https://github.com/openai/codex.git
synced 2026-11-13 06:45:07 +00:00
Add Windows sandbox service lifecycle scaffolding (#42341)
## What changed - Add the `codex-windows-sandbox-service` workspace crate and Windows-only build target. - Integrate `CodexSandboxService` with the Windows Service Control Manager, including start, stop, shutdown, status reporting, and event-log handling. - Support service mode by default and a debug-only `--foreground` mode. Keep provisioning IPC disabled until authenticated request handling is available. ## Testing - Add argument-parsing tests for service, foreground, unknown, and multiple arguments. GitOrigin-RevId: 1c84f0a593b65e5cf97ab7606cde03675542b0b0
This commit is contained in:
1 parent
dcfcb570b2
commit
501931b399
9 files changed
+445
No files matched your search
Generated
+8
@@ -4972,6 +4972,14 @@ dependencies = [
|
|||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "codex-windows-sandbox-service"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "codex-workload-identity"
|
name = "codex-workload-identity"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ members = [
|
|||||||
"tools",
|
"tools",
|
||||||
"v8-poc",
|
"v8-poc",
|
||||||
"websocket-client",
|
"websocket-client",
|
||||||
|
"windows-sandbox-service",
|
||||||
"worktree",
|
"worktree",
|
||||||
"workload-identity",
|
"workload-identity",
|
||||||
"utils/absolute-path",
|
"utils/absolute-path",
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
load("//:defs.bzl", "codex_rust_crate")
|
||||||
|
|
||||||
|
codex_rust_crate(
|
||||||
|
name = "windows-sandbox-service",
|
||||||
|
binary_test_target_compatible_with = ["@platforms//os:windows"],
|
||||||
|
crate_name = "codex_windows_sandbox_service",
|
||||||
|
)
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
[package]
|
||||||
|
name = "codex-windows-sandbox-service"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "codex_windows_sandbox_service"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
doctest = false
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "codex-windows-sandbox-service"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = { workspace = true }
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies.windows-sys]
|
||||||
|
version = "0.52"
|
||||||
|
features = [
|
||||||
|
"Win32_Foundation",
|
||||||
|
"Win32_NetworkManagement_NetManagement",
|
||||||
|
"Win32_Security",
|
||||||
|
"Win32_Security_Authorization",
|
||||||
|
"Win32_Storage_FileSystem",
|
||||||
|
"Win32_Storage_Packaging_Appx",
|
||||||
|
"Win32_System_Com",
|
||||||
|
"Win32_System_Environment",
|
||||||
|
"Win32_System_EventLog",
|
||||||
|
"Win32_System_IO",
|
||||||
|
"Win32_System_Memory",
|
||||||
|
"Win32_System_Pipes",
|
||||||
|
"Win32_System_Services",
|
||||||
|
"Win32_System_SystemServices",
|
||||||
|
"Win32_System_Threading",
|
||||||
|
"Win32_UI_Shell",
|
||||||
|
]
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
//! Disabled provisioning IPC until authenticated request handling is installed.
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
|
|
||||||
|
pub(crate) const PIPE_NAME: &str = r"\\.\pipe\OpenAI.CodexSandbox";
|
||||||
|
|
||||||
|
pub(crate) fn run(
|
||||||
|
_shutdown: Arc<AtomicBool>,
|
||||||
|
_on_ready: impl FnOnce() -> Result<()>,
|
||||||
|
) -> Result<()> {
|
||||||
|
anyhow::bail!("sandbox provisioning IPC is disabled until authenticated handling is installed")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn wake() {}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
mod ipc;
|
||||||
|
#[cfg(windows)]
|
||||||
|
mod service;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub enum RunMode {
|
||||||
|
Service,
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
Foreground,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(mode: RunMode) -> Result<()> {
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
match mode {
|
||||||
|
RunMode::Service => service::run(),
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
RunMode::Foreground => service::run_foreground(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
{
|
||||||
|
let _ = mode;
|
||||||
|
anyhow::bail!("the Codex sandbox service is only available on Windows")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use codex_windows_sandbox_service::RunMode;
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
codex_windows_sandbox_service::run(parse_mode(std::env::args().skip(1))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_mode(arguments: impl Iterator<Item = String>) -> Result<RunMode> {
|
||||||
|
let arguments = arguments.collect::<Vec<_>>();
|
||||||
|
match arguments.as_slice() {
|
||||||
|
[] => Ok(RunMode::Service),
|
||||||
|
[argument] if argument == "--service" => Ok(RunMode::Service),
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
[argument] if argument == "--foreground" => Ok(RunMode::Foreground),
|
||||||
|
[argument] => anyhow::bail!("unrecognized service argument {argument:?}"),
|
||||||
|
_ => anyhow::bail!("expected at most one service argument"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "main_tests.rs"]
|
||||||
|
mod tests;
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use super::RunMode;
|
||||||
|
use super::parse_mode;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn defaults_to_service_mode() -> Result<()> {
|
||||||
|
assert_eq!(parse_mode(std::iter::empty())?, RunMode::Service);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn accepts_explicit_service_mode() -> Result<()> {
|
||||||
|
let arguments = [String::from("--service")].into_iter();
|
||||||
|
assert_eq!(parse_mode(arguments)?, RunMode::Service);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
fn accepts_foreground_mode() -> Result<()> {
|
||||||
|
let arguments = [String::from("--foreground")].into_iter();
|
||||||
|
assert_eq!(parse_mode(arguments)?, RunMode::Foreground);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
fn rejects_foreground_mode_in_release_builds() {
|
||||||
|
let arguments = [String::from("--foreground")].into_iter();
|
||||||
|
assert!(parse_mode(arguments).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_unknown_arguments() {
|
||||||
|
let arguments = [String::from("--unknown")].into_iter();
|
||||||
|
assert!(parse_mode(arguments).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_multiple_arguments() {
|
||||||
|
let arguments = [String::from("--service"), String::from("--foreground")].into_iter();
|
||||||
|
assert!(parse_mode(arguments).is_err());
|
||||||
|
}
|
||||||
@@ -0,0 +1,276 @@
|
|||||||
|
//! Windows service lifecycle and event-log integration for sandbox provisioning.
|
||||||
|
|
||||||
|
use std::ffi::c_void;
|
||||||
|
use std::io;
|
||||||
|
use std::ptr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
|
use std::sync::atomic::AtomicU32;
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use anyhow::Result;
|
||||||
|
use windows_sys::Win32::Foundation::ERROR_CALL_NOT_IMPLEMENTED;
|
||||||
|
use windows_sys::Win32::Foundation::ERROR_SERVICE_SPECIFIC_ERROR;
|
||||||
|
use windows_sys::Win32::Foundation::NO_ERROR;
|
||||||
|
use windows_sys::Win32::System::EventLog::DeregisterEventSource;
|
||||||
|
use windows_sys::Win32::System::EventLog::EVENTLOG_ERROR_TYPE;
|
||||||
|
use windows_sys::Win32::System::EventLog::EVENTLOG_INFORMATION_TYPE;
|
||||||
|
use windows_sys::Win32::System::EventLog::RegisterEventSourceW;
|
||||||
|
use windows_sys::Win32::System::EventLog::ReportEventW;
|
||||||
|
use windows_sys::Win32::System::Services::RegisterServiceCtrlHandlerExW;
|
||||||
|
use windows_sys::Win32::System::Services::SERVICE_ACCEPT_SHUTDOWN;
|
||||||
|
use windows_sys::Win32::System::Services::SERVICE_ACCEPT_STOP;
|
||||||
|
use windows_sys::Win32::System::Services::SERVICE_CONTROL_INTERROGATE;
|
||||||
|
use windows_sys::Win32::System::Services::SERVICE_CONTROL_SHUTDOWN;
|
||||||
|
use windows_sys::Win32::System::Services::SERVICE_CONTROL_STOP;
|
||||||
|
use windows_sys::Win32::System::Services::SERVICE_RUNNING;
|
||||||
|
use windows_sys::Win32::System::Services::SERVICE_START_PENDING;
|
||||||
|
use windows_sys::Win32::System::Services::SERVICE_STATUS;
|
||||||
|
use windows_sys::Win32::System::Services::SERVICE_STATUS_HANDLE;
|
||||||
|
use windows_sys::Win32::System::Services::SERVICE_STOP_PENDING;
|
||||||
|
use windows_sys::Win32::System::Services::SERVICE_STOPPED;
|
||||||
|
use windows_sys::Win32::System::Services::SERVICE_TABLE_ENTRYW;
|
||||||
|
use windows_sys::Win32::System::Services::SERVICE_WIN32_OWN_PROCESS;
|
||||||
|
use windows_sys::Win32::System::Services::SetServiceStatus;
|
||||||
|
use windows_sys::Win32::System::Services::StartServiceCtrlDispatcherW;
|
||||||
|
|
||||||
|
pub(crate) const SERVICE_NAME: &str = "CodexSandboxService";
|
||||||
|
const EVENT_SERVICE_STARTED: u32 = 1000;
|
||||||
|
const EVENT_SERVICE_STOP_REQUESTED: u32 = 1001;
|
||||||
|
const EVENT_SERVICE_STOPPED: u32 = 1002;
|
||||||
|
const EVENT_SERVICE_FAILED: u32 = 1003;
|
||||||
|
const MAX_EVENT_MESSAGE_UNITS: usize = 1024;
|
||||||
|
|
||||||
|
static SERVICE_STATE: OnceLock<ServiceState> = OnceLock::new();
|
||||||
|
|
||||||
|
struct ServiceState {
|
||||||
|
shutdown: Arc<AtomicBool>,
|
||||||
|
status_handle: OnceLock<SERVICE_STATUS_HANDLE>,
|
||||||
|
current_status: AtomicU32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn run() -> Result<()> {
|
||||||
|
let state = ServiceState {
|
||||||
|
shutdown: Arc::new(AtomicBool::new(false)),
|
||||||
|
status_handle: OnceLock::new(),
|
||||||
|
current_status: AtomicU32::new(SERVICE_START_PENDING),
|
||||||
|
};
|
||||||
|
SERVICE_STATE
|
||||||
|
.set(state)
|
||||||
|
.map_err(|_| anyhow::anyhow!("the service dispatcher was already initialized"))?;
|
||||||
|
|
||||||
|
let mut service_name = SERVICE_NAME
|
||||||
|
.encode_utf16()
|
||||||
|
.chain(std::iter::once(0))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let service_table = [
|
||||||
|
SERVICE_TABLE_ENTRYW {
|
||||||
|
lpServiceName: service_name.as_mut_ptr(),
|
||||||
|
lpServiceProc: Some(service_main),
|
||||||
|
},
|
||||||
|
SERVICE_TABLE_ENTRYW {
|
||||||
|
lpServiceName: ptr::null_mut(),
|
||||||
|
lpServiceProc: None,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// The table and its service-name buffer remain alive until the dispatcher returns.
|
||||||
|
let dispatched = unsafe { StartServiceCtrlDispatcherW(service_table.as_ptr()) };
|
||||||
|
if dispatched == 0 {
|
||||||
|
return Err(io::Error::last_os_error()).context("start the Windows service dispatcher");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
pub(crate) fn run_foreground() -> Result<()> {
|
||||||
|
crate::ipc::run(Arc::new(AtomicBool::new(false)), || {
|
||||||
|
eprintln!("{SERVICE_NAME} listening on {}", crate::ipc::PIPE_NAME);
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "system" fn service_main(_argument_count: u32, _arguments: *mut *mut u16) {
|
||||||
|
let Some(state) = SERVICE_STATE.get() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(error) = service_main_inner(state) {
|
||||||
|
log_error(
|
||||||
|
EVENT_SERVICE_FAILED,
|
||||||
|
&format!("The Codex sandbox service encountered a fatal error: {error:#}"),
|
||||||
|
);
|
||||||
|
eprintln!("{SERVICE_NAME} failed: {error:#}");
|
||||||
|
if state.status_handle.get().is_some() {
|
||||||
|
let _ = state.report_status(SERVICE_STOPPED, ERROR_SERVICE_SPECIFIC_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn service_main_inner(state: &ServiceState) -> Result<()> {
|
||||||
|
let service_name = SERVICE_NAME
|
||||||
|
.encode_utf16()
|
||||||
|
.chain(std::iter::once(0))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// The service controller only reads the name during registration.
|
||||||
|
let status_handle = unsafe {
|
||||||
|
RegisterServiceCtrlHandlerExW(
|
||||||
|
service_name.as_ptr(),
|
||||||
|
Some(service_control_handler),
|
||||||
|
ptr::null(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if status_handle == 0 {
|
||||||
|
return Err(io::Error::last_os_error()).context("register the service control handler");
|
||||||
|
}
|
||||||
|
state
|
||||||
|
.status_handle
|
||||||
|
.set(status_handle)
|
||||||
|
.map_err(|_| anyhow::anyhow!("the service status handle was already registered"))?;
|
||||||
|
|
||||||
|
state.report_status(SERVICE_START_PENDING, NO_ERROR)?;
|
||||||
|
crate::ipc::run(Arc::clone(&state.shutdown), || {
|
||||||
|
state.report_status(SERVICE_RUNNING, NO_ERROR)?;
|
||||||
|
log_information(
|
||||||
|
EVENT_SERVICE_STARTED,
|
||||||
|
"The Codex sandbox service is running.",
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.context("run the sandbox provisioning broker")?;
|
||||||
|
|
||||||
|
log_information(
|
||||||
|
EVENT_SERVICE_STOPPED,
|
||||||
|
"The Codex sandbox service has stopped.",
|
||||||
|
);
|
||||||
|
state.report_status(SERVICE_STOPPED, NO_ERROR)
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "system" fn service_control_handler(
|
||||||
|
control: u32,
|
||||||
|
_event_type: u32,
|
||||||
|
_event_data: *mut c_void,
|
||||||
|
_context: *mut c_void,
|
||||||
|
) -> u32 {
|
||||||
|
let Some(state) = SERVICE_STATE.get() else {
|
||||||
|
return ERROR_CALL_NOT_IMPLEMENTED;
|
||||||
|
};
|
||||||
|
|
||||||
|
match control {
|
||||||
|
SERVICE_CONTROL_STOP | SERVICE_CONTROL_SHUTDOWN => {
|
||||||
|
if !state.shutdown.swap(true, Ordering::AcqRel) {
|
||||||
|
if let Err(error) = state.report_status(SERVICE_STOP_PENDING, NO_ERROR) {
|
||||||
|
eprintln!("unable to report service shutdown: {error:#}");
|
||||||
|
}
|
||||||
|
crate::ipc::wake();
|
||||||
|
log_information(
|
||||||
|
EVENT_SERVICE_STOP_REQUESTED,
|
||||||
|
"The Codex sandbox service was asked to stop.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
NO_ERROR
|
||||||
|
}
|
||||||
|
SERVICE_CONTROL_INTERROGATE => {
|
||||||
|
let current_status = state.current_status.load(Ordering::Acquire);
|
||||||
|
if let Err(error) = state.report_status(current_status, NO_ERROR) {
|
||||||
|
log_error(
|
||||||
|
EVENT_SERVICE_FAILED,
|
||||||
|
"The Codex sandbox service could not report its current status.",
|
||||||
|
);
|
||||||
|
eprintln!("unable to report the current service status: {error:#}");
|
||||||
|
}
|
||||||
|
NO_ERROR
|
||||||
|
}
|
||||||
|
_ => ERROR_CALL_NOT_IMPLEMENTED,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn log_information(event_id: u32, message: &str) {
|
||||||
|
log_event(EVENTLOG_INFORMATION_TYPE, event_id, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn log_error(event_id: u32, message: &str) {
|
||||||
|
log_event(EVENTLOG_ERROR_TYPE, event_id, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log_event(event_type: u16, event_id: u32, message: &str) {
|
||||||
|
let source = SERVICE_NAME
|
||||||
|
.encode_utf16()
|
||||||
|
.chain(std::iter::once(0))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let event_log = unsafe { RegisterEventSourceW(ptr::null(), source.as_ptr()) };
|
||||||
|
if event_log == 0 {
|
||||||
|
eprintln!("unable to open the Windows event log: {message}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut message_units = Vec::with_capacity(MAX_EVENT_MESSAGE_UNITS + 1);
|
||||||
|
for character in message.chars() {
|
||||||
|
let character = if character.is_control() {
|
||||||
|
' '
|
||||||
|
} else {
|
||||||
|
character
|
||||||
|
};
|
||||||
|
let mut buffer = [0_u16; 2];
|
||||||
|
let encoded = character.encode_utf16(&mut buffer);
|
||||||
|
if message_units.len() + encoded.len() > MAX_EVENT_MESSAGE_UNITS {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
message_units.extend_from_slice(encoded);
|
||||||
|
}
|
||||||
|
message_units.push(0);
|
||||||
|
let strings = [message_units.as_ptr()];
|
||||||
|
let reported = unsafe {
|
||||||
|
ReportEventW(
|
||||||
|
event_log,
|
||||||
|
event_type,
|
||||||
|
0,
|
||||||
|
event_id,
|
||||||
|
ptr::null_mut(),
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
strings.as_ptr(),
|
||||||
|
ptr::null(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if reported == 0 {
|
||||||
|
eprintln!("unable to write a Windows event-log entry (event {event_id})");
|
||||||
|
}
|
||||||
|
unsafe { DeregisterEventSource(event_log) };
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServiceState {
|
||||||
|
fn report_status(&self, current_status: u32, win32_exit_code: u32) -> Result<()> {
|
||||||
|
let status_handle = *self
|
||||||
|
.status_handle
|
||||||
|
.get()
|
||||||
|
.context("the service status handle was not registered")?;
|
||||||
|
let is_pending = matches!(current_status, SERVICE_START_PENDING | SERVICE_STOP_PENDING);
|
||||||
|
let status = SERVICE_STATUS {
|
||||||
|
dwServiceType: SERVICE_WIN32_OWN_PROCESS,
|
||||||
|
dwCurrentState: current_status,
|
||||||
|
dwControlsAccepted: if current_status == SERVICE_RUNNING {
|
||||||
|
SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
},
|
||||||
|
dwWin32ExitCode: win32_exit_code,
|
||||||
|
dwServiceSpecificExitCode: u32::from(win32_exit_code == ERROR_SERVICE_SPECIFIC_ERROR),
|
||||||
|
dwCheckPoint: u32::from(is_pending),
|
||||||
|
dwWaitHint: if is_pending { 10_000 } else { 0 },
|
||||||
|
};
|
||||||
|
|
||||||
|
self.current_status.store(current_status, Ordering::Release);
|
||||||
|
// The SCM synchronously copies the status structure during this call.
|
||||||
|
let updated = unsafe { SetServiceStatus(status_handle, &status) };
|
||||||
|
if updated == 0 {
|
||||||
|
return Err(io::Error::last_os_error()).context("update the Windows service status");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user