Files
codex/codex-rs/session/src/spawn.rs
2025-04-27 12:42:27 -07:00

153 lines
5.0 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Spawn detached Codex agent processes for exec and repl sessions.
use crate::store::Paths;
use anyhow::Context;
use anyhow::Result;
use std::fs::OpenOptions;
use tokio::process::Child;
use tokio::process::Command;
// -------------------------------------------------------------------------
// Additional (Unix-only) imports to replace the former unsafe `libc` calls.
// These are guarded by `cfg(unix)` so Windows builds are completely unaffected.
// -------------------------------------------------------------------------
#[cfg(unix)]
use command_group::AsyncCommandGroup; // provides `group_spawn` for tokio::process::Command
#[cfg(unix)]
use nix::{
errno::Errno,
sys::{
stat::Mode,
},
unistd::mkfifo,
};
/// Open (and create if necessary) the log files that stdout / stderr of the
/// spawned agent will be redirected to.
fn open_log_files(paths: &Paths) -> Result<(std::fs::File, std::fs::File)> {
let stdout = OpenOptions::new()
.create(true)
.append(true)
.open(&paths.stdout)?;
let stderr = OpenOptions::new()
.create(true)
.append(true)
.open(&paths.stderr)?;
Ok((stdout, stderr))
}
/// Configure a `tokio::process::Command` with the common options that are the
/// same for both `codex-exec` and `codex-repl` sessions.
fn base_command(bin: &str, paths: &Paths) -> Result<Command> {
let (stdout, stderr) = open_log_files(paths)?;
let mut cmd = Command::new(bin);
cmd.stdin(std::process::Stdio::null())
.stdout(stdout)
.stderr(stderr);
Ok(cmd)
}
pub fn spawn_exec(paths: &Paths, exec_args: &[String]) -> Result<Child> {
#[cfg(unix)]
{
// -----------------------------------------------------------------
// UNIX IMPLEMENTATION (now 100 % safe)
// -----------------------------------------------------------------
// Build the base command and add the user-supplied arguments.
let mut cmd = base_command("codex-exec", paths)?;
cmd.args(exec_args);
// Replace the `stdin` that `base_command` configured (null) with
// `/dev/null` opened for reading keeps the previous behaviour while
// still leveraging the common helper.
let stdin = OpenOptions::new().read(true).open("/dev/null")?;
cmd.stdin(stdin);
// Spawn the child as a *process group* / new session leader.
// `group_spawn()` internally performs the traditional
// 1. `fork()`
// 2. `setsid()`
// 3. `execvp()`
// sequence that we previously had to code manually via an unsafe
// `pre_exec` closure.
let child = cmd
.group_spawn() // <- safe wrapper from the `command-group` crate
.context("failed to spawn codex-exec")?
.into_inner(); // convert AsyncGroupChild -> tokio::process::Child
// Ignore SIGHUP in the parent, mirroring the behaviour of the previous
// unsafe `libc::signal` call.
crate::sig::ignore_sighup()?;
Ok(child)
}
#[cfg(windows)]
{
use std::os::windows::process::CommandExt;
const DETACHED_PROCESS: u32 = 0x00000008;
const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
let mut cmd = base_command("codex-exec", paths)?;
cmd.args(exec_args)
.creation_flags(DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP);
let child = cmd.spawn().context("failed to spawn codex-exec")?;
Ok(child)
}
}
pub fn spawn_repl(paths: &Paths, repl_args: &[String]) -> Result<Child> {
#[cfg(unix)]
{
// -----------------------------------------------------------------
// UNIX IMPLEMENTATION (now 100 % safe)
// -----------------------------------------------------------------
// Ensure a FIFO exists at `paths.stdin` with permissions rw-------
if !paths.stdin.exists() {
if let Err(e) = mkfifo(&paths.stdin, Mode::from_bits_truncate(0o600)) {
// If the FIFO already exists we silently accept, just as the
// previous implementation did.
if e != Errno::EEXIST {
return Err(std::io::Error::from(e)).context("mkfifo failed");
}
}
}
// Open the FIFO for *both* reading and writing so we don't deadlock
// when there is no writer yet (mimics the previous behaviour).
let stdin = OpenOptions::new()
.read(true)
.write(true)
.open(&paths.stdin)?;
// Build the command.
let mut cmd = base_command("codex-repl", paths)?;
cmd.args(repl_args).stdin(stdin);
// Detached spawn.
let child = cmd
.group_spawn()
.context("failed to spawn codex-repl")?
.into_inner();
// Ignore SIGHUP as before.
crate::sig::ignore_sighup()?;
Ok(child)
}
#[cfg(windows)]
{
anyhow::bail!("codex-repl sessions are not supported on Windows yet");
}
}