Skip to content

Commit 0cb5dfd

Browse files
committed
impl preexec_fn
1 parent 014622a commit 0cb5dfd

File tree

2 files changed

+24
-19
lines changed

2 files changed

+24
-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: 24 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,18 @@ 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 like CPython
235+
let _ = write!(pipe, "SubprocessError:0:{}", ctx.as_msg());
236+
} else {
237+
let _ = write!(pipe, "OSError:{}:{}", e as i32, ctx.as_msg());
238+
}
237239
std::process::exit(255)
238240
}
239241
}
@@ -242,6 +244,7 @@ fn exec(args: &ForkExecArgs<'_>, procargs: ProcArgs<'_>) -> ! {
242244
enum ExecErrorContext {
243245
NoExec,
244246
ChDir,
247+
PreExec,
245248
Exec,
246249
}
247250

@@ -250,6 +253,7 @@ impl ExecErrorContext {
250253
match self {
251254
Self::NoExec => "noexec",
252255
Self::ChDir => "noexec:chdir",
256+
Self::PreExec => "Exception occurred in preexec_fn.",
253257
Self::Exec => "",
254258
}
255259
}
@@ -259,6 +263,7 @@ fn exec_inner(
259263
args: &ForkExecArgs<'_>,
260264
procargs: ProcArgs<'_>,
261265
ctx: &mut ExecErrorContext,
266+
vm: &VirtualMachine,
262267
) -> nix::Result<Never> {
263268
for &fd in args.fds_to_keep.as_slice() {
264269
if fd.as_raw_fd() != args.errpipe_write.as_raw_fd() {
@@ -345,6 +350,18 @@ fn exec_inner(
345350
nix::Error::result(ret)?;
346351
}
347352

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

350367
if args.close_fds {

0 commit comments

Comments
 (0)