Skip to content

Commit 4f0b940

Browse files
authored
impl preexec_fn (#6479)
1 parent 309b2ad commit 4f0b940

File tree

2 files changed

+25
-19
lines changed

2 files changed

+25
-19
lines changed

Lib/test/test_subprocess.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2244,8 +2244,6 @@ def test_CalledProcessError_str_non_zero(self):
22442244
error_string = str(err)
22452245
self.assertIn("non-zero exit status 2.", error_string)
22462246

2247-
# TODO: RUSTPYTHON
2248-
@unittest.expectedFailure
22492247
def test_preexec(self):
22502248
# DISCLAIMER: Setting environment variables is *not* a good use
22512249
# of a preexec_fn. This is merely a test.
@@ -2257,8 +2255,6 @@ def test_preexec(self):
22572255
with p:
22582256
self.assertEqual(p.stdout.read(), b"apple")
22592257

2260-
# TODO: RUSTPYTHON
2261-
@unittest.expectedFailure
22622258
def test_preexec_exception(self):
22632259
def raise_it():
22642260
raise ValueError("What if two swallows carried a coconut?")
@@ -2300,8 +2296,6 @@ def _execute_child(self, *args, **kwargs):
23002296
for fd in devzero_fds:
23012297
os.close(fd)
23022298

2303-
# TODO: RUSTPYTHON
2304-
@unittest.expectedFailure
23052299
@unittest.skipIf(not os.path.exists("/dev/zero"), "/dev/zero required.")
23062300
def test_preexec_errpipe_does_not_double_close_pipes(self):
23072301
"""Issue16140: Don't double close pipes on preexec error."""
@@ -2339,8 +2333,6 @@ def test_preexec_gc_module_failure(self):
23392333
if not enabled:
23402334
gc.disable()
23412335

2342-
# TODO: RUSTPYTHON
2343-
@unittest.expectedFailure
23442336
@unittest.skipIf(
23452337
sys.platform == 'darwin', 'setrlimit() seems to fail on OS X')
23462338
def test_preexec_fork_failure(self):
@@ -2751,8 +2743,6 @@ def test_swap_std_fds_with_one_closed(self):
27512743
for to_fds in itertools.permutations(range(3), 2):
27522744
self._check_swap_std_fds_with_one_closed(from_fds, to_fds)
27532745

2754-
# TODO: RUSTPYTHON
2755-
@unittest.expectedFailure
27562746
def test_surrogates_error_message(self):
27572747
def prepare():
27582748
raise ValueError("surrogate:\uDCff")
@@ -3228,8 +3218,6 @@ def test_leak_fast_process_del_killed(self):
32283218
else:
32293219
self.assertNotIn(ident, [id(o) for o in subprocess._active])
32303220

3231-
# TODO: RUSTPYTHON
3232-
@unittest.expectedFailure
32333221
def test_close_fds_after_preexec(self):
32343222
fd_status = support.findfile("fd_status.py", subdir="subprocessdata")
32353223

crates/stdlib/src/posixsubprocess.rs

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,6 @@ mod _posixsubprocess {
3333

3434
#[pyfunction]
3535
fn fork_exec(args: ForkExecArgs<'_>, vm: &VirtualMachine) -> PyResult<libc::pid_t> {
36-
if args.preexec_fn.is_some() {
37-
return Err(vm.new_not_implemented_error("preexec_fn not supported yet"));
38-
}
3936
let extra_groups = args
4037
.groups_list
4138
.as_ref()
@@ -49,7 +46,7 @@ mod _posixsubprocess {
4946
extra_groups: extra_groups.as_deref(),
5047
};
5148
match unsafe { nix::unistd::fork() }.map_err(|err| err.into_pyexception(vm))? {
52-
nix::unistd::ForkResult::Child => exec(&args, procargs),
49+
nix::unistd::ForkResult::Child => exec(&args, procargs, vm),
5350
nix::unistd::ForkResult::Parent { child } => Ok(child.as_raw()),
5451
}
5552
}
@@ -227,13 +224,19 @@ struct ProcArgs<'a> {
227224
extra_groups: Option<&'a [Gid]>,
228225
}
229226

230-
fn exec(args: &ForkExecArgs<'_>, procargs: ProcArgs<'_>) -> ! {
227+
fn exec(args: &ForkExecArgs<'_>, procargs: ProcArgs<'_>, vm: &VirtualMachine) -> ! {
231228
let mut ctx = ExecErrorContext::NoExec;
232-
match exec_inner(args, procargs, &mut ctx) {
229+
match exec_inner(args, procargs, &mut ctx, vm) {
233230
Ok(x) => match x {},
234231
Err(e) => {
235232
let mut pipe = args.errpipe_write;
236-
let _ = write!(pipe, "OSError:{}:{}", e as i32, ctx.as_msg());
233+
if matches!(ctx, ExecErrorContext::PreExec) {
234+
// For preexec_fn errors, use SubprocessError format (errno=0)
235+
let _ = write!(pipe, "SubprocessError:0:{}", ctx.as_msg());
236+
} else {
237+
// errno is written in hex format
238+
let _ = write!(pipe, "OSError:{:x}:{}", e as i32, ctx.as_msg());
239+
}
237240
std::process::exit(255)
238241
}
239242
}
@@ -242,6 +245,7 @@ fn exec(args: &ForkExecArgs<'_>, procargs: ProcArgs<'_>) -> ! {
242245
enum ExecErrorContext {
243246
NoExec,
244247
ChDir,
248+
PreExec,
245249
Exec,
246250
}
247251

@@ -250,6 +254,7 @@ impl ExecErrorContext {
250254
match self {
251255
Self::NoExec => "noexec",
252256
Self::ChDir => "noexec:chdir",
257+
Self::PreExec => "Exception occurred in preexec_fn.",
253258
Self::Exec => "",
254259
}
255260
}
@@ -259,6 +264,7 @@ fn exec_inner(
259264
args: &ForkExecArgs<'_>,
260265
procargs: ProcArgs<'_>,
261266
ctx: &mut ExecErrorContext,
267+
vm: &VirtualMachine,
262268
) -> nix::Result<Never> {
263269
for &fd in args.fds_to_keep.as_slice() {
264270
if fd.as_raw_fd() != args.errpipe_write.as_raw_fd() {
@@ -345,6 +351,18 @@ fn exec_inner(
345351
nix::Error::result(ret)?;
346352
}
347353

354+
// Call preexec_fn after all process setup but before closing FDs
355+
if let Some(ref preexec_fn) = args.preexec_fn {
356+
match preexec_fn.call((), vm) {
357+
Ok(_) => {}
358+
Err(_e) => {
359+
// Cannot safely stringify exception after fork
360+
*ctx = ExecErrorContext::PreExec;
361+
return Err(Errno::UnknownErrno);
362+
}
363+
}
364+
}
365+
348366
*ctx = ExecErrorContext::Exec;
349367

350368
if args.close_fds {

0 commit comments

Comments
 (0)