Enable authenticated Windows sandbox provisioning (#42351)

## Why

The Windows sandbox service had provisioning policy and client authentication in place, but its IPC listener was still disabled.

## What changed

- Serve framed provisioning requests over a local named pipe, authenticate packaged clients, validate requests and machine policy, and report provisioning outcomes through bounded responses and Windows event logging.
- Return `unavailable` for configuration parse failures so clients can fall back to the elevated setup helper.
- Keep validated directory handles alive in the setup helper so path protections survive an unexpected service exit.
- Make connection recovery and shutdown wakeups tolerate clients that disconnect before the listener accepts them.

## Testing

Added Windows tests for response framing, configuration-error classification, pipe security and reconnect behavior, shutdown wakeups, and retained helper handles.

GitOrigin-RevId: 68e9d546dcada88ff162ea4f98c0f4d748706b34
This commit is contained in:
johnl-oai authored and copyberry committed 2026-09-02 20:27:05 +00:00
1 parent 4fdf4c1113
commit 7e45bdb5fd
12 files changed
+857 -59

No files matched your search

@@ -23,8 +23,6 @@ codex-config = { workspace = true }
codex-core = { workspace = true }
codex-windows-sandbox = { workspace = true }
tokio = { workspace = true, features = ["macros", "rt", "sync", "time"] }
[dev-dependencies]
toml = { workspace = true }
[target.'cfg(windows)'.dependencies.windows-sys]
+356 -14
View File
@@ -1,36 +1,378 @@
//! Disabled provisioning IPC until authenticated request handling is installed.
//! Authenticated local IPC for the Windows sandbox provisioning service.
//! Configuration parse failures defer provisioning to the client's elevated helper.
//! Shutdown wakeups are retried until the listener connects or stops.
// Keep these private until the authenticated transport is connected.
#[allow(dead_code)]
mod authentication;
#[allow(dead_code)]
mod home;
#[allow(dead_code)]
mod request;
use anyhow::Context;
use anyhow::Result;
#[cfg(test)]
use anyhow::bail;
use authentication::authenticate_client;
use codex_windows_sandbox::FramedProvisioningMessage;
use codex_windows_sandbox::PROVISIONING_PROTOCOL_VERSION;
use codex_windows_sandbox::ProvisioningMessage;
use codex_windows_sandbox::SandboxProvisioningResponse;
use codex_windows_sandbox::ensure_sandbox_users_group;
use codex_windows_sandbox::run_elevated_provisioning_setup_with_retained_handles;
use codex_windows_sandbox::sandbox_setup_is_complete_with_settings;
use codex_windows_sandbox::string_from_sid_bytes;
use codex_windows_sandbox::to_wide;
use codex_windows_sandbox::write_provisioning_frame;
use home::OwnedHandle;
#[cfg(test)]
use home::pin_existing_ancestors;
#[cfg(test)]
use request::ProvisioningRequest;
#[cfg(test)]
use request::validate_request;
use std::mem::size_of;
use std::os::windows::fs::MetadataExt;
use std::os::windows::io::BorrowedHandle;
use std::ptr;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::time::Duration;
use std::time::Instant;
use windows_sys::Win32::Foundation as foundation;
use windows_sys::Win32::Foundation::HANDLE;
use windows_sys::Win32::Security as security;
use windows_sys::Win32::Security::Authorization as authorization;
use windows_sys::Win32::Storage::FileSystem as filesystem;
use windows_sys::Win32::System::Pipes as pipes;
pub(crate) const PIPE_NAME: &str = codex_windows_sandbox::SANDBOX_PROVISIONING_PIPE_NAME;
pub(crate) const PIPE_NAME: &str = r"\\.\pipe\OpenAI.CodexSandbox";
const MAX_REQUEST_BYTES: usize = 4096;
const MAX_RESPONSE_MESSAGE_BYTES: usize = 512;
const REQUEST_IDLE_TIMEOUT: Duration = Duration::from_secs(5);
const PIPE_USER_ACCESS: &str = "0x0012019b";
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")
struct SecurityDescriptor(security::PSECURITY_DESCRIPTOR);
impl Drop for SecurityDescriptor {
fn drop(&mut self) {
unsafe { foundation::LocalFree(self.0 as foundation::HLOCAL) };
}
}
pub(crate) fn wake() {}
#[derive(Debug, Eq, PartialEq)]
enum PipeConnection {
Connected,
Disconnected,
}
pub(crate) fn run(shutdown: Arc<AtomicBool>, on_ready: impl FnOnce() -> Result<()>) -> Result<()> {
let sandbox_sid = ensure_sandbox_users_group()?;
let sid_string = string_from_sid_bytes(&sandbox_sid).map_err(anyhow::Error::msg)?;
let sddl = pipe_security_descriptor(&sid_string);
let mut descriptor: security::PSECURITY_DESCRIPTOR = ptr::null_mut();
if unsafe {
authorization::ConvertStringSecurityDescriptorToSecurityDescriptorW(
to_wide(sddl).as_ptr(),
authorization::SDDL_REVISION_1,
&mut descriptor,
ptr::null_mut(),
)
} == 0
{
return Err(std::io::Error::last_os_error()).context("create provisioning pipe DACL");
}
let descriptor = SecurityDescriptor(descriptor);
let attributes = security::SECURITY_ATTRIBUTES {
nLength: size_of::<security::SECURITY_ATTRIBUTES>() as u32,
lpSecurityDescriptor: descriptor.0,
bInheritHandle: 0,
};
let pipe = unsafe {
pipes::CreateNamedPipeW(
to_wide(PIPE_NAME).as_ptr(),
filesystem::PIPE_ACCESS_DUPLEX | filesystem::FILE_FLAG_FIRST_PIPE_INSTANCE,
pipes::PIPE_TYPE_BYTE
| pipes::PIPE_READMODE_BYTE
| pipes::PIPE_WAIT
| pipes::PIPE_REJECT_REMOTE_CLIENTS,
1,
1024,
MAX_REQUEST_BYTES as u32,
0,
&attributes,
)
};
if pipe == foundation::INVALID_HANDLE_VALUE {
return Err(std::io::Error::last_os_error()).context("create provisioning pipe");
}
let pipe = OwnedHandle(pipe);
on_ready().context("publish provisioning listener readiness")?;
while !shutdown.load(Ordering::Acquire) {
if accept_pipe_connection(pipe.0)? == PipeConnection::Disconnected {
continue;
}
if shutdown.load(Ordering::Acquire) {
break;
}
let authorized_process = match crate::package_identity::authorize_client_process(pipe.0) {
Ok(process) => process,
Err(_) => {
unsafe { pipes::DisconnectNamedPipe(pipe.0) };
continue;
}
};
let result = handle_request(pipe.0, &authorized_process, &sandbox_sid, &shutdown);
let response = match result {
Ok(response) => response,
Err(error) => {
eprintln!("sandbox provisioning request failed: {error}");
let mut message = String::new();
for character in error.to_string().chars() {
let character = if character.is_control() {
' '
} else {
character
};
if message.len() + character.len_utf8() > MAX_RESPONSE_MESSAGE_BYTES {
break;
}
message.push(character);
}
SandboxProvisioningResponse::Error { message }
}
};
let response = FramedProvisioningMessage {
version: PROVISIONING_PROTOCOL_VERSION,
message: ProvisioningMessage::ProvisionSandboxResponse { payload: response },
};
let mut frame = Vec::new();
write_provisioning_frame(&mut frame, &response)
.context("serialize sandbox provisioning response")?;
let mut written = 0;
let sent = unsafe {
filesystem::WriteFile(
pipe.0,
frame.as_ptr(),
frame.len() as u32,
&mut written,
ptr::null_mut(),
)
};
if sent != 0 {
let deadline = Instant::now() + Duration::from_secs(1);
while !shutdown.load(Ordering::Acquire) && Instant::now() < deadline {
if unsafe {
pipes::PeekNamedPipe(
pipe.0,
ptr::null_mut(),
0,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
)
} == 0
{
break;
}
std::thread::sleep(Duration::from_millis(10));
}
}
unsafe { pipes::DisconnectNamedPipe(pipe.0) };
}
Ok(())
}
fn accept_pipe_connection(pipe: HANDLE) -> Result<PipeConnection> {
if unsafe { pipes::ConnectNamedPipe(pipe, ptr::null_mut()) } != 0 {
return Ok(PipeConnection::Connected);
}
let error = unsafe { foundation::GetLastError() };
match error {
foundation::ERROR_PIPE_CONNECTED => Ok(PipeConnection::Connected),
foundation::ERROR_NO_DATA | foundation::ERROR_BROKEN_PIPE => {
if unsafe { pipes::DisconnectNamedPipe(pipe) } == 0 {
let reset_error = std::io::Error::last_os_error();
if reset_error.raw_os_error() != Some(foundation::ERROR_PIPE_NOT_CONNECTED as i32) {
return Err(reset_error).context("reset disconnected provisioning client");
}
}
Ok(PipeConnection::Disconnected)
}
_ => Err(std::io::Error::from_raw_os_error(error as i32))
.context("accept provisioning client"),
}
}
pub(crate) fn wake(pipe_name: &str, is_stopped: impl Fn() -> bool) {
let pipe_name = to_wide(pipe_name);
while !is_stopped() {
let handle = unsafe {
filesystem::CreateFileW(
pipe_name.as_ptr(),
foundation::GENERIC_WRITE,
/*dwsharemode*/ 0,
ptr::null(),
filesystem::OPEN_EXISTING,
/*dwflagsandattributes*/ 0,
/*htemplatefile*/ 0,
)
};
if handle != foundation::INVALID_HANDLE_VALUE {
unsafe { foundation::CloseHandle(handle) };
return;
}
// A disconnected instance cannot accept the wakeup until ConnectNamedPipe runs.
std::thread::sleep(Duration::from_millis(25));
}
}
fn pipe_security_descriptor(sandbox_sid: &str) -> String {
format!("D:P(D;;GA;;;{sandbox_sid})(A;;GA;;;SY)(A;;GA;;;BA)(A;;{PIPE_USER_ACCESS};;;IU)")
}
fn handle_request(
pipe: HANDLE,
authorized_process: &crate::package_identity::AuthorizedClientProcess,
sandbox_sid: &[u8],
shutdown: &AtomicBool,
) -> Result<SandboxProvisioningResponse> {
let deadline = Instant::now() + REQUEST_IDLE_TIMEOUT;
let mut request = [0_u8; MAX_REQUEST_BYTES];
let mut request_length = 0;
loop {
if shutdown.load(Ordering::Acquire) {
bail!("service is stopping");
}
if Instant::now() >= deadline {
bail!("provisioning request timed out");
}
let mut available = 0;
if unsafe {
pipes::PeekNamedPipe(
pipe,
ptr::null_mut(),
0,
ptr::null_mut(),
&mut available,
ptr::null_mut(),
)
} == 0
{
return Err(std::io::Error::last_os_error()).context("inspect provisioning request");
}
if available as usize > MAX_REQUEST_BYTES - request_length {
bail!("provisioning request exceeds size limit");
}
if available != 0 {
let mut read = 0;
if unsafe {
filesystem::ReadFile(
pipe,
request[request_length..].as_mut_ptr(),
available,
&mut read,
ptr::null_mut(),
)
} == 0
{
return Err(std::io::Error::last_os_error()).context("read provisioning request");
}
if read == 0 {
bail!("provisioning client sent an empty request");
}
request_length += read as usize;
let received = &request[..request_length];
if received.len() >= size_of::<u32>() {
let payload_length =
u32::from_le_bytes([received[0], received[1], received[2], received[3]])
as usize;
if payload_length > MAX_REQUEST_BYTES - size_of::<u32>() {
bail!("provisioning request exceeds size limit");
}
let frame_length = size_of::<u32>() + payload_length;
if request_length > frame_length {
bail!("provisioning requests must contain exactly one IPC frame");
}
if request_length == frame_length {
break;
}
}
continue;
}
std::thread::sleep(Duration::from_millis(25));
}
let request = validate_request(&request[..request_length])?;
let (identity, policy_result) =
authenticate_client(pipe, authorized_process, sandbox_sid, &request)?;
if let Err(error) = policy_result {
if is_config_parse_error(&error) {
return Ok(SandboxProvisioningResponse::Unavailable);
}
crate::service::log_error(
crate::service::EVENT_REQUEST_REJECTED,
&format!("Codex sandbox provisioning was rejected by administrator policy: {error}"),
);
return Err(error)
.context("requested sandbox settings violate administrator-controlled machine policy");
}
if sandbox_setup_is_complete_with_settings(&identity.codex_home, &request.settings) {
return Ok(SandboxProvisioningResponse::Ok);
}
let helper = std::env::current_exe()
.context("locate the provisioning service executable")?
.with_file_name("codex-windows-sandbox-setup.exe");
let helper_metadata = helper
.symlink_metadata()
.with_context(|| format!("inspect packaged setup helper {}", helper.display()))?;
if !helper_metadata.is_file()
|| helper_metadata.file_attributes() & filesystem::FILE_ATTRIBUTE_REPARSE_POINT != 0
{
bail!(
"refusing invalid packaged setup helper {}",
helper.display()
);
}
let retained_handles = identity
.directory_handles
.iter()
// The identity owns these handles through the synchronous helper launch and wait.
.map(|handle| unsafe { BorrowedHandle::borrow_raw(handle.0 as _) })
.collect::<Vec<_>>();
match run_elevated_provisioning_setup_with_retained_handles(
&identity.codex_home,
&identity.account,
request.settings,
&retained_handles,
) {
Ok(()) => {
crate::service::log_information(
crate::service::EVENT_PROVISIONING_SUCCEEDED,
"Codex sandbox provisioning completed successfully.",
);
Ok(SandboxProvisioningResponse::Ok)
}
Err(error) => {
crate::service::log_error(
crate::service::EVENT_PROVISIONING_FAILED,
&format!("Codex sandbox provisioning failed: {error}"),
);
Err(error).context("sandbox provisioning failed")
}
}
}
fn is_config_parse_error(error: &anyhow::Error) -> bool {
error.chain().any(|cause| {
cause.is::<toml::de::Error>()
|| cause
.downcast_ref::<std::io::Error>()
.and_then(std::io::Error::get_ref)
.is_some_and(<dyn std::error::Error + Send + Sync>::is::<toml::de::Error>)
})
}
#[cfg(test)]
#[path = "ipc_tests.rs"]
@@ -20,8 +20,8 @@ use super::request::ProvisioningRequest;
pub(super) struct ClientIdentity {
pub(super) account: String,
pub(super) codex_home: PathBuf,
// Denying FILE_SHARE_DELETE pins every checked directory through provisioning.
_directory_handles: Vec<OwnedHandle>,
// Retained by both the service and helper throughout provisioning.
pub(super) directory_handles: Vec<OwnedHandle>,
}
pub(super) fn authenticate_client(
@@ -144,7 +144,7 @@ fn authenticate_impersonated_client(
ClientIdentity {
account,
codex_home,
_directory_handles: handles,
directory_handles: handles,
},
token,
))
+218 -19
View File
@@ -1,7 +1,12 @@
use super::OwnedHandle;
use super::PipeConnection;
use super::ProvisioningRequest;
use super::accept_pipe_connection;
use super::is_config_parse_error;
use super::pin_existing_ancestors;
use super::pipe_security_descriptor;
use super::validate_request;
use super::wake;
use codex_windows_sandbox::FramedProvisioningMessage;
use codex_windows_sandbox::PROVISIONING_PROTOCOL_VERSION;
use codex_windows_sandbox::ProvisioningMessage;
@@ -9,12 +14,17 @@ use codex_windows_sandbox::SandboxProvisioningRequest;
use codex_windows_sandbox::SandboxProvisioningResponse;
use codex_windows_sandbox::WindowsSandboxProvisioningSettings;
use codex_windows_sandbox::WindowsSandboxProxyListeners;
use codex_windows_sandbox::read_provisioning_frame;
use codex_windows_sandbox::to_wide;
use codex_windows_sandbox::write_provisioning_frame;
use std::path::Path;
use std::path::PathBuf;
use std::ptr;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
use std::sync::mpsc;
use std::time::Duration;
use std::time::Instant;
use windows_sys::Win32::Foundation as foundation;
use windows_sys::Win32::Storage::FileSystem as filesystem;
use windows_sys::Win32::Storage::Packaging::Appx;
@@ -33,6 +43,67 @@ fn framed_request(request: SandboxProvisioningRequest) -> Vec<u8> {
frame
}
#[test]
fn unavailable_response_round_trips_through_provisioning_frame() {
let mut frame = Vec::new();
write_provisioning_frame(
&mut frame,
&FramedProvisioningMessage {
version: PROVISIONING_PROTOCOL_VERSION,
message: ProvisioningMessage::ProvisionSandboxResponse {
payload: SandboxProvisioningResponse::Unavailable,
},
},
)
.unwrap();
assert!(matches!(
read_provisioning_frame(frame.as_slice()).unwrap(),
Some(FramedProvisioningMessage {
version: PROVISIONING_PROTOCOL_VERSION,
message: ProvisioningMessage::ProvisionSandboxResponse {
payload: SandboxProvisioningResponse::Unavailable,
},
})
));
}
#[test]
fn config_parse_errors_are_distinguished_from_policy_and_io_failures() {
let schema_error = codex_core::config::deserialize_config_toml_with_base(
toml::from_str(r#"model_verbosity = "future-verbosity""#).unwrap(),
Path::new(r"C:\CodexTest"),
)
.unwrap_err();
let contents = "[";
let parse_error = toml::from_str::<toml::Value>(contents).unwrap_err();
let syntax_error = codex_config::io_error_from_config_error(
std::io::ErrorKind::InvalidData,
codex_config::config_error_from_toml("config.toml", contents, parse_error.clone()),
Some(parse_error),
);
for error in [schema_error, syntax_error] {
assert!(is_config_parse_error(
&anyhow::Error::new(error).context("load managed configuration"),
));
}
for error in [
anyhow::anyhow!("managed policy does not permit the elevated Windows sandbox"),
anyhow::Error::new(std::io::Error::from_raw_os_error(
foundation::ERROR_ACCESS_DENIED as i32,
)),
anyhow::Error::new(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"invalid credentials",
)),
] {
assert!(!is_config_parse_error(
&error.context("load managed configuration"),
));
}
}
#[test]
fn provisioning_request_preserves_home_spaces_and_unicode() {
let request = framed_request(SandboxProvisioningRequest {
@@ -214,25 +285,6 @@ fn provisioning_request_rejects_empty_control_characters_and_invalid_utf8() {
assert!(validate_request(&invalid_utf8).is_err());
}
#[test]
fn pin_existing_ancestors_accepts_drive_and_verbatim_drive_roots() {
let executable = std::env::current_exe().unwrap().canonicalize().unwrap();
let verbatim_root = executable.ancestors().last().unwrap();
let ordinary_root = PathBuf::from(
verbatim_root
.to_str()
.unwrap()
.strip_prefix(r"\\?\")
.unwrap(),
);
for root in [ordinary_root.as_path(), verbatim_root] {
let mut handles = Vec::new();
pin_existing_ancestors(root, &mut handles).unwrap();
assert_eq!(handles.len(), 1);
}
}
#[test]
fn unpackaged_pipe_clients_are_rejected_before_sending_a_request() {
let mut family_length = 0;
@@ -297,3 +349,150 @@ fn unpackaged_pipe_clients_are_rejected_before_sending_a_request() {
"unexpected package authorization failure: {error:#}"
);
}
#[test]
fn disconnected_pipe_clients_do_not_prevent_subsequent_connections() {
let pipe_name = format!(
r"\\.\pipe\OpenAI.CodexSandbox.DisconnectTests.{}",
std::process::id()
);
let name = to_wide(&pipe_name);
let server = unsafe {
pipes::CreateNamedPipeW(
name.as_ptr(),
filesystem::PIPE_ACCESS_DUPLEX | filesystem::FILE_FLAG_FIRST_PIPE_INSTANCE,
pipes::PIPE_TYPE_BYTE
| pipes::PIPE_READMODE_BYTE
| pipes::PIPE_WAIT
| pipes::PIPE_REJECT_REMOTE_CLIENTS,
1,
1024,
1024,
0,
ptr::null(),
)
};
assert_ne!(server, foundation::INVALID_HANDLE_VALUE);
let server = OwnedHandle(server);
let first_client = unsafe {
filesystem::CreateFileW(
name.as_ptr(),
foundation::GENERIC_READ | foundation::GENERIC_WRITE,
0,
ptr::null(),
filesystem::OPEN_EXISTING,
0,
0,
)
};
assert_ne!(first_client, foundation::INVALID_HANDLE_VALUE);
drop(OwnedHandle(first_client));
assert_eq!(
accept_pipe_connection(server.0).unwrap(),
PipeConnection::Disconnected
);
let server_handle = server.0;
let listener = std::thread::spawn(move || accept_pipe_connection(server_handle));
let available = unsafe { pipes::WaitNamedPipeW(name.as_ptr(), 5_000) };
assert_ne!(
available,
0,
"wait for replacement named-pipe listener: {}",
std::io::Error::last_os_error()
);
let next_client = unsafe {
filesystem::CreateFileW(
name.as_ptr(),
foundation::GENERIC_READ | foundation::GENERIC_WRITE,
0,
ptr::null(),
filesystem::OPEN_EXISTING,
0,
0,
)
};
assert_ne!(
next_client,
foundation::INVALID_HANDLE_VALUE,
"connect replacement named-pipe client: {}",
std::io::Error::last_os_error()
);
let next_client = OwnedHandle(next_client);
assert_eq!(
listener.join().expect("join named-pipe listener").unwrap(),
PipeConnection::Connected
);
drop(next_client);
assert_ne!(unsafe { pipes::DisconnectNamedPipe(server.0) }, 0);
let timeout = Duration::from_secs(5);
let deadline = Instant::now() + timeout;
let (checks_tx, checks_rx) = mpsc::channel();
let waker = std::thread::spawn(move || {
wake(&pipe_name, || {
let _ = checks_tx.send(());
Instant::now() >= deadline
});
});
checks_rx.recv_timeout(timeout).unwrap();
// The second check proves a wake failed before the listener started reconnecting.
checks_rx.recv_timeout(timeout).unwrap();
let (accepted_tx, accepted_rx) = mpsc::channel();
let listener = std::thread::spawn(move || {
let _ = accepted_tx.send(accept_pipe_connection(server.0));
drop(server);
});
let accepted = accepted_rx.recv_timeout(timeout);
waker.join().expect("join shutdown waker");
// The wake handle may close before ConnectNamedPipe observes the connection.
assert!(
accepted
.expect("shutdown wake should unblock the listener")
.is_ok()
);
listener.join().expect("join shutdown listener");
}
#[test]
fn unexpected_pipe_connection_errors_remain_fatal() {
let error = accept_pipe_connection(foundation::INVALID_HANDLE_VALUE).unwrap_err();
assert_eq!(
error
.downcast_ref::<std::io::Error>()
.and_then(std::io::Error::raw_os_error),
Some(foundation::ERROR_INVALID_HANDLE as i32)
);
}
#[test]
fn pin_existing_ancestors_accepts_drive_and_verbatim_drive_roots() {
let executable = std::env::current_exe().unwrap().canonicalize().unwrap();
let verbatim_root = executable.ancestors().last().unwrap();
let ordinary_root = PathBuf::from(
verbatim_root
.to_str()
.unwrap()
.strip_prefix(r"\\?\")
.unwrap(),
);
for root in [ordinary_root.as_path(), verbatim_root] {
let mut handles = Vec::new();
pin_existing_ancestors(root, &mut handles).unwrap();
assert_eq!(handles.len(), 1);
}
}
#[test]
fn pipe_descriptor_denies_sandbox_group_before_interactive_users() {
let descriptor = pipe_security_descriptor("S-1-5-21-11-12-13-14");
let deny = descriptor.find("(D;;GA;;;S-1-5-21-11-12-13-14)").unwrap();
let interactive = descriptor.find("(A;;0x0012019b;;;IU)").unwrap();
assert!(deny < interactive);
}
@@ -2,12 +2,9 @@ use anyhow::Result;
#[cfg(windows)]
mod ipc;
// Provisioning remains disabled until authenticated transport connects this policy.
#[cfg(windows)]
#[allow(dead_code)]
mod machine_policy;
#[cfg(windows)]
#[allow(dead_code)]
mod package_identity;
#[cfg(windows)]
mod service;
@@ -41,6 +41,9 @@ 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;
pub(crate) const EVENT_PROVISIONING_SUCCEEDED: u32 = 2000;
pub(crate) const EVENT_PROVISIONING_FAILED: u32 = 2001;
pub(crate) const EVENT_REQUEST_REJECTED: u32 = 2002;
const MAX_EVENT_MESSAGE_UNITS: usize = 1024;
static SERVICE_STATE: OnceLock<ServiceState> = OnceLock::new();
@@ -167,7 +170,11 @@ unsafe extern "system" fn service_control_handler(
if let Err(error) = state.report_status(SERVICE_STOP_PENDING, NO_ERROR) {
eprintln!("unable to report service shutdown: {error:#}");
}
crate::ipc::wake();
std::thread::spawn(move || {
crate::ipc::wake(crate::ipc::PIPE_NAME, || {
state.current_status.load(Ordering::Acquire) == SERVICE_STOPPED
});
});
log_information(
EVENT_SERVICE_STOP_REQUESTED,
"The Codex sandbox service was asked to stop.",