mirror of
https://github.com/openai/codex.git
synced 2026-10-27 18:48:37 +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:
Generated
+8
@@ -4972,6 +4972,14 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-windows-sandbox-service"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-workload-identity"
|
||||
version = "0.0.0"
|
||||
|
||||
@@ -103,6 +103,7 @@ members = [
|
||||
"tools",
|
||||
"v8-poc",
|
||||
"websocket-client",
|
||||
"windows-sandbox-service",
|
||||
"worktree",
|
||||
"workload-identity",
|
||||
"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