Files
codex/codex-rs/utils/cargo-bin/src/executable.rs
T
jif 18344a972d Centralize executable fixture creation to avoid Linux ETXTBSY races (#48727)
## Why

Concurrent tests can inherit writable descriptors for executable fixtures, causing `ETXTBSY` when those fixtures launch on Linux.

## What changed

Expose shared `write_executable` and `copy_executable` helpers from `codex_utils_cargo_bin` and adopt them in CLI, exec-server, and MCP tests. On Linux, writes and copies complete in separate processes so sibling test spawns cannot inherit the writable descriptors. Script fixtures receive mode `0o755`, and copied executables retain their source permissions.

Remove the executable-busy retry loop from the program resolver test now that its script uses the shared helper.

GitOrigin-RevId: 61624158190bb1e75c27d9eb9c87a2defc147fb7
2026-09-27 15:25:13 +00:00

63 lines
2.2 KiB
Rust

//! Creates executable test fixtures without exposing writable descriptors to sibling spawns.
//! On Linux, writes and copies finish in a separate process before the fixture can be launched.
use std::fs;
use std::io;
use std::path::Path;
#[cfg(target_os = "linux")]
use std::process::Command;
/// Writes a small Unix script fixture and makes it executable with mode 0o755.
#[cfg(unix)]
pub fn write_executable(path: &Path, script: &str) -> io::Result<()> {
use std::os::unix::fs::PermissionsExt;
#[cfg(target_os = "linux")]
{
// A sibling test can fork while a parent-owned file is open for writing.
// CLOEXEC does not release that inherited descriptor until the sibling execs.
let output = Command::new("/bin/sh")
.arg("-c")
.arg("printf '%s' \"$1\" > \"$2\"")
.arg("write-executable-fixture")
.arg(script)
.arg(path)
.output()?;
if !output.status.success() {
return Err(io::Error::other(format!(
"failed to write executable fixture {}: {}",
path.display(),
String::from_utf8_lossy(&output.stderr),
)));
}
}
#[cfg(not(target_os = "linux"))]
fs::write(path, script)?;
fs::set_permissions(path, fs::Permissions::from_mode(/*mode*/ 0o755))
}
/// Copies an executable fixture, preserving the source permissions like [`fs::copy`].
pub fn copy_executable(source: &Path, destination: &Path) -> io::Result<()> {
#[cfg(target_os = "linux")]
{
let output = Command::new("/bin/cp")
.arg("--")
.arg(source)
.arg(destination)
.output()?;
if !output.status.success() {
return Err(io::Error::other(format!(
"failed to copy executable fixture {} to {}: {}",
source.display(),
destination.display(),
String::from_utf8_lossy(&output.stderr),
)));
}
// Match fs::copy even when the test process has a restrictive umask.
fs::set_permissions(destination, fs::metadata(source)?.permissions())?;
}
#[cfg(not(target_os = "linux"))]
fs::copy(source, destination)?;
Ok(())
}