Repository: rust
Issue: std::process:Command::spawn panics when pre_exec hook fails if SIGCHLD is ignored
Labels: C-bug,T-libs,A-io,O-unix,A-process

On Linux when a `CommandExt::pre_exec` hook is in use, `std::process::Command::spawn` forks, calls `do_exec` and then runs each of the hooks in order. If `do_exec` fails (if a pre_exec hook or exec itself fails for example), the `Err` bubbles up and the child process writes the errno to the pipe of the parent before exiting.

After the parent reads the error, it calls `Command::wait` which internally calls `waitpid(2)`. When a `SIGCHLD` handler is installed - in this case, `SIG_IGN` - there is no child to wait for, so `waitpid(2)` returns `ECHILD`. This leads to `spawn` panicking:

```
thread 'main' panicked at 'wait() should either return Ok or panic', library/std/src/sys/unix/process/process_unix.rs:129:21
```

I expected to be able to return an error from `pre_exec` hooks without causing a panic, even if `SIGCHLD` is handled.

The `pre_exec` hook is a prerequisite in order to disable the `posix_spawn` path. Note that there are other ways to disable `posix_spawn`, which would reproduce the issue as well.

### Reproduction

The issue can be reproduced as follows:

```rust
// Built with libc 0.2.141
fn main() {
    use std::os::unix::process::CommandExt;
    let mut cmd = std::process::Command::new("hopefully invalid path");

    unsafe {
        libc::signal(libc::SIGCHLD, libc::SIG_IGN);
        cmd.pre_exec(|| Ok(()));
    }

    cmd.spawn().unwrap();
}
```

Which results in
```
thread 'main' panicked at 'wait() should either return Ok or panic', library/std/src/sys/unix/process/process_unix.rs:129:21
stack backtrace:
   0: rust_begin_unwind
             at /rustc/5e1d3299a290026b85787bc9c7e72bcc53ac283f/library/std/src/panicking.rs:577:5
   1: core::panicking::panic_fmt
             at /rustc/5e1d3299a290026b85787bc9c7e72bcc53ac283f/library/core/src/panicking.rs:67:14
   2: std::sys::unix::process::process_inner::<impl std::sys::unix::process::process_common::Command>::spawn
   3: std::process::Command::spawn
             at /rustc/5e1d3299a290026b85787bc9c7e72bcc53ac283f/library/std/src/process.rs:893:9
   4: rust_test::main
             at ./src/main.rs:10:5
   5: core::ops::function::FnOnce::call_once
             at /rustc/5e1d3299a290026b85787bc9c7e72bcc53ac283f/library/core/src/ops/function.rs:250:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
```

The waitpid behavior can also be observed in plain C with:

```c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>

int main() {
	signal(SIGCHLD, SIG_IGN);
	pid_t pid = fork();
	if (pid == -1) {
		fprintf(stderr, "fork failed: %d, %s\n", errno, strerror(errno));
		return 1;
	}

	if (pid == 0) {
		fprintf(stderr, "child!\n");
		return 0;
	}

	int status = 0;
	int res = waitpid(pid, &status, 0);
	fprintf(stderr, "res: %d, status: %d, errno: %d, %s\n", res, status, errno, strerror(errno));
	return 0;
}
```

Which outputs:
```
child!
res: -1, errno: 10, No child processes⏎
```

If the signal handler is commented out, `waitpid` works as the `Command::spawn` expects.

### Meta

- `rustc --version --verbose`:
```
rustc 1.70.0-nightly (5e1d3299a 2023-03-31)
binary: rustc
commit-hash: 5e1d3299a290026b85787bc9c7e72bcc53ac283f
commit-date: 2023-03-31
host: x86_64-unknown-linux-gnu
release: 1.70.0-nightly
LLVM version: 16.0.0
```
- Kernel: Linux 6.2.10-arch1-1
- glibc: 2.37-2