Codex Handbook
windows-sandbox-rs/src/conpty/mod.rs 158 lines
//! ConPTY helpers for spawning sandboxed processes with a PTY on Windows.//!//! This module encapsulates ConPTY creation and process spawn with the required//! `PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE` plumbing. It is shared by both the legacy//! restricted‑token path and the elevated runner path when unified_exec runs with//! `tty=true`. The helpers are not tied to the IPC layer and can be reused by other//! Windows sandbox flows that need a PTY.use crate::desktop::LaunchDesktop;use crate::proc_thread_attr::ProcThreadAttributeList;use crate::winutil::format_last_error;use crate::winutil::quote_windows_arg;use crate::winutil::to_wide;use anyhow::Result;use codex_utils_pty::PsuedoCon;use codex_utils_pty::RawConPty;use std::collections::HashMap;use std::ffi::c_void;use std::os::windows::io::IntoRawHandle;use std::path::Path;use windows_sys::Win32::Foundation::CloseHandle;use windows_sys::Win32::Foundation::GetLastError;use windows_sys::Win32::Foundation::HANDLE;use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;use windows_sys::Win32::System::Threading::CREATE_UNICODE_ENVIRONMENT;use windows_sys::Win32::System::Threading::CreateProcessAsUserW;use windows_sys::Win32::System::Threading::EXTENDED_STARTUPINFO_PRESENT;use windows_sys::Win32::System::Threading::PROCESS_INFORMATION;use windows_sys::Win32::System::Threading::STARTF_USESTDHANDLES;use windows_sys::Win32::System::Threading::STARTUPINFOEXW;use crate::process::make_env_block;/// Owns a ConPTY handle and its backing pipe handles.pub struct ConptyInstance {    pseudoconsole: Option<PsuedoCon>,    input_write: HANDLE,    output_read: HANDLE,    _desktop: Option<LaunchDesktop>,}impl Drop for ConptyInstance {    fn drop(&mut self) {        unsafe {            if self.input_write != 0 && self.input_write != INVALID_HANDLE_VALUE {                CloseHandle(self.input_write);            }            if self.output_read != 0 && self.output_read != INVALID_HANDLE_VALUE {                CloseHandle(self.output_read);            }        }        let _ = self.pseudoconsole.take();    }}impl ConptyInstance {    pub fn raw_handle(&self) -> Option<HANDLE> {        self.pseudoconsole            .as_ref()            .map(|pseudoconsole| pseudoconsole.raw_handle() as HANDLE)    }    pub fn take_input_write(&mut self) -> HANDLE {        std::mem::replace(&mut self.input_write, 0)    }    pub fn take_output_read(&mut self) -> HANDLE {        std::mem::replace(&mut self.output_read, 0)    }}/// Create a ConPTY with backing pipes.////// This is public so callers that need lower-level PTY setup can build on the same/// primitive, although the common entry point is `spawn_conpty_process_as_user`.#[allow(dead_code)]pub fn create_conpty(cols: i16, rows: i16) -> Result<ConptyInstance> {    let raw = RawConPty::new(cols, rows)?;    let (pseudoconsole, input_write, output_read) = raw.into_handles();    Ok(ConptyInstance {        pseudoconsole: Some(pseudoconsole),        input_write: input_write.into_raw_handle() as HANDLE,        output_read: output_read.into_raw_handle() as HANDLE,        _desktop: None,    })}/// Spawn a process under `h_token` with ConPTY attached.////// This is the main shared ConPTY entry point and is used by both the legacy/direct path/// and the elevated runner path whenever a PTY-backed sandboxed process is needed.pub fn spawn_conpty_process_as_user(    h_token: HANDLE,    argv: &[String],    cwd: &Path,    env_map: &HashMap<String, String>,    use_private_desktop: bool,    logs_base_dir: Option<&Path>,) -> Result<(PROCESS_INFORMATION, ConptyInstance)> {    let cmdline_str = argv        .iter()        .map(|arg| quote_windows_arg(arg))        .collect::<Vec<_>>()        .join(" ");    let mut cmdline: Vec<u16> = to_wide(&cmdline_str);    let env_block = make_env_block(env_map);    let mut si: STARTUPINFOEXW = unsafe { std::mem::zeroed() };    si.StartupInfo.cb = std::mem::size_of::<STARTUPINFOEXW>() as u32;    si.StartupInfo.dwFlags = STARTF_USESTDHANDLES;    si.StartupInfo.hStdInput = INVALID_HANDLE_VALUE;    si.StartupInfo.hStdOutput = INVALID_HANDLE_VALUE;    si.StartupInfo.hStdError = INVALID_HANDLE_VALUE;    let desktop = LaunchDesktop::prepare(use_private_desktop, logs_base_dir)?;    si.StartupInfo.lpDesktop = desktop.startup_info_desktop();    let raw = RawConPty::new(/*cols*/ 80, /*rows*/ 24)?;    let (pseudoconsole, input_write, output_read) = raw.into_handles();    let hpc = pseudoconsole.raw_handle() as HANDLE;    let conpty = ConptyInstance {        pseudoconsole: Some(pseudoconsole),        input_write: input_write.into_raw_handle() as HANDLE,        output_read: output_read.into_raw_handle() as HANDLE,        _desktop: Some(desktop),    };    let mut attrs = ProcThreadAttributeList::new(/*attr_count*/ 1)?;    attrs.set_pseudoconsole(hpc)?;    si.lpAttributeList = attrs.as_mut_ptr();    let mut pi: PROCESS_INFORMATION = unsafe { std::mem::zeroed() };    let ok = unsafe {        CreateProcessAsUserW(            h_token,            std::ptr::null(),            cmdline.as_mut_ptr(),            std::ptr::null_mut(),            std::ptr::null_mut(),            0,            EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT,            env_block.as_ptr() as *mut c_void,            to_wide(cwd).as_ptr(),            &si.StartupInfo,            &mut pi,        )    };    if ok == 0 {        let err = unsafe { GetLastError() } as i32;        return Err(anyhow::anyhow!(            "CreateProcessAsUserW failed: {} ({}) | cwd={} | cmd={} | env_u16_len={}",            err,            format_last_error(err),            cwd.display(),            cmdline_str,            env_block.len()        ));    }    Ok((pi, conpty))}