Commit Graph

45 Commits

Author SHA1 Message Date
Dan Gohman
cada5fb336 Update PidFd for the new I/O safety APIs. 2021-08-19 12:02:40 -07:00
Dominik Stolz
c3321d3eb3 Add tracking issue and link to man-page 2021-07-21 10:49:11 +02:00
Dominik Stolz
619fd96868 Add PidFd type and seal traits
Improve docs

Split do_fork into two

Make do_fork unsafe

Add target attribute to create_pidfd field in Command

Add method to get create_pidfd value
2021-07-21 10:49:11 +02:00
Josh Triplett
ef03de2e6a Typo fix
Co-authored-by: bjorn3 <bjorn3@users.noreply.github.com>
2021-07-21 10:49:11 +02:00
Aaron Hill
694be09b7b Add Linux-specific pidfd process extensions
Background:

Over the last year, pidfd support was added to the Linux kernel. This
allows interacting with other processes. In particular, this allows
waiting on a child process with a timeout in a race-free way, bypassing
all of the awful signal-handler tricks that are usually required.

Pidfds can be obtained for a child process (as well as any other
process) via the `pidfd_open` syscall. Unfortunately, this requires
several conditions to hold in order to be race-free (i.e. the pid is not
reused).
Per `man pidfd_open`:

```
· the disposition of SIGCHLD has not been explicitly set to SIG_IGN
 (see sigaction(2));

· the SA_NOCLDWAIT flag was not specified while establishing a han‐
 dler for SIGCHLD or while setting the disposition of that signal to
 SIG_DFL (see sigaction(2)); and

· the zombie process was not reaped elsewhere in the program (e.g.,
 either by an asynchronously executed signal handler or by wait(2)
 or similar in another thread).

If any of these conditions does not hold, then the child process
(along with a PID file descriptor that refers to it) should instead
be created using clone(2) with the CLONE_PIDFD flag.
```

Sadly, these conditions are impossible to guarantee once any libraries
are used. For example, C code runnng in a different thread could call
`wait()`, which is impossible to detect from Rust code trying to open a
pidfd.

While pid reuse issues should (hopefully) be rare in practice, we can do
better. By passing the `CLONE_PIDFD` flag to `clone()` or `clone3()`, we
can obtain a pidfd for the child process in a guaranteed race-free
manner.

This PR:

This PR adds Linux-specific process extension methods to allow obtaining
pidfds for processes spawned via the standard `Command` API. Other than
being made available to user code, the standard library does not make
use of these pidfds in any way. In particular, the implementation of
`Child::wait` is completely unchanged.

Two Linux-specific helper methods are added: `CommandExt::create_pidfd`
and `ChildExt::pidfd`. These methods are intended to serve as a building
block for libraries to build higher-level abstractions - in particular,
waiting on a process with a timeout.

I've included a basic test, which verifies that pidfds are created iff
the `create_pidfd` method is used. This test is somewhat special - it
should always succeed on systems with the `clone3` system call
available, and always fail on systems without `clone3` available. I'm
not sure how to best ensure this programatically.

This PR relies on the newer `clone3` system call to pass the `CLONE_FD`,
rather than the older `clone` system call. `clone3` was added to Linux
in the same release as pidfds, so this shouldn't unnecessarily limit the
kernel versions that this code supports.

Unresolved questions:
* What should the name of the feature gate be for these newly added
  methods?
* Should the `pidfd` method distinguish between an error occurring
  and `create_pidfd` not being called?
2021-07-21 10:49:11 +02:00
Aris Merchant
fd0cb0cdc2 Change weak! and linkat! to macros 2.0
`weak!` is needed in a test in another module. With macros
1.0, importing `weak!` would require reordering module
declarations in `std/src/lib.rs`, which is a bit too
evil.
2021-07-10 12:55:09 -07:00
Christiaan Dirkx
63791233ff Add ExitStatusError for vxworks 2021-05-20 01:34:06 +02:00
bors
25a277f03d Auto merge of #82973 - ijackson:exitstatuserror, r=yaahc
Provide ExitStatusError

Closes #73125

In MR #81452 "Add #[must_use] to [...] process::ExitStatus" we concluded that the existing arrangements in are too awkward so adding that `#[must_use]` is blocked on improving the ergonomics.

I wrote a mini-RFC-style discusion of the approach in https://github.com/rust-lang/rust/issues/73125#issuecomment-771092741
2021-05-18 08:01:32 +00:00
Ian Jackson
e893089ea0 Provide ExitStatusError
Closes #73125

This is in pursuance of
  Issue #73127 Consider adding #[must_use] to std::process::ExitStatus

In
  MR #81452 Add #[must_use] to [...] process::ExitStatus
we concluded that the existing arrangements in are too awkward
so adding that #[must_use] is blocked on improving the ergonomics.

I wrote a mini-RFC-style discusion of the approach in
  https://github.com/rust-lang/rust/issues/73125#issuecomment-771092741

Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
2021-05-12 11:12:19 +01:00
Ian Jackson
820123a949 panic/fork: Command: Do not unwind after fork() in child
Unwinding after fork() in the child is UB on some platforms, because
on those (including musl) malloc can be UB in the child of a
multithreaded program, and unwinding must box for the payload.

Even if it's safe, unwinding past fork() in the child causes whatever
traps the unwind to return twice.  This is very strange and clearly
not desirable.  With the default behaviour of the thread library, this
can even result in a panic in the child being transformed into zero
exit status (ie, success) as seen in the parent!

Fixes #79740.

Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
2021-05-07 11:17:44 +01:00
Josh Triplett
68dbdfb5bf Simplify Command::spawn (no semantic change)
This minimizes the size of an unsafe block, and allows outdenting some
complex code.
2021-03-29 13:37:24 -07:00
Yuki Okushi
3f41fdd2eb
Rollup merge of #83462 - ijackson:exitstatus-message-wording, r=joshtriplett
ExitStatus: print "exit status: {}" rather than "exit code: {}" on unix

Proper Unix terminology is "exit status" (vs "wait status").  "exit
code" is imprecise on Unix and therefore unclear.  (As far as I can
tell, "exit code" is correct terminology on Windows.)

This new wording is unfortunately inconsistent with the identifier
names in the Rust stdlib.

It is the identifier names that are wrong, as discussed at length in eg
  https://doc.rust-lang.org/nightly/std/process/struct.ExitStatus.html
  https://doc.rust-lang.org/nightly/std/os/unix/process/trait.ExitStatusExt.html

Unfortunately for API stability reasons it would be a lot of work, and
a lot of disruption, to change the names in the stdlib (eg to rename
`std::process::ExitStatus` to `std::process::ChildStatus` or
something), but we should fix the message output.  Many (probably
most) readers of these messages about exit statuses will be users and
system administrators, not programmers, who won't even know that Rust
has this wrong terminology.

So I think the right thing is to fix the documentation (as I have
already done) and, now, the terminology in the implementation.

This is a user-visible change to the behaviour of all Rust programs
which run Unix subprocesses.  Hopefully no-one is matching against the
exit status string, except perhaps in tests.
2021-03-28 01:33:15 +09:00
Ian Jackson
11e40ce240 ExitStatus: print "exit status: {}" rather than "exit code: {}"
Proper Unix terminology is "exit status" (vs "wait status").  "exit
code" is imprecise on Unix and therefore unclear.  (As far as I can
tell, "exit code" is correct terminology on Windows.)

This new wording is unfortunately inconsistent with the identifier
names in the Rust stdlib.

It is the identifier names that are wrong, as discussed at length in eg
  https://doc.rust-lang.org/nightly/std/process/struct.ExitStatus.html
  https://doc.rust-lang.org/nightly/std/os/unix/process/trait.ExitStatusExt.html

Unfortunately for API stability reasons it would be a lot of work, and
a lot of disruption, to change the names in the stdlib (eg to rename
`std::process::ExitStatus` to `std::process::ChildStatus` or
something), but we should fix the message output.  Many (probably
most) readers of these messages about exit statuses will be users and
system administrators, not programmers, who won't even know that Rust
has this wrong terminology.

So I think the right thing is to fix the documentation (as I have
already done) and, now, the terminology in the implementation.

This is a user-visible change to the behaviour of all Rust programs
which run Unix subprocesses.  Hopefully no-one is matching against the
exit status string, except perhaps in tests.

Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
2021-03-25 10:27:53 +00:00
Dylan DPC
a42e62fa0a
Rollup merge of #83353 - m-ou-se:io-error-avoid-alloc, r=nagisa
Add internal io::Error::new_const to avoid allocations.

This makes it possible to have a io::Error containing a message with zero allocations, and uses that everywhere to avoid the *three* allocations involved in `io::Error::new(kind, "message")`.

The function signature isn't perfect, because it needs a reference to the `&str`. So for now, this is just a `pub(crate)` function. Later, we'll be able to use `fn new_const<MSG: &'static str>(kind: ErrorKind)` to make that a bit better. (Then we'll also be able to use some ZST trickery if that would result in more efficient code.)

See https://github.com/rust-lang/rust/issues/83352
2021-03-24 01:52:29 +01:00
Mara Bos
7b71719faf Use io::Error::new_const everywhere to avoid allocations. 2021-03-21 20:22:38 +01:00
The8472
e22143c075 Revert "Revert "use RWlock when accessing os::env #81850""
This reverts commit acdca316c3.
2021-03-14 19:10:34 +01:00
Dylan DPC
d01648b60e
Rollup merge of #82949 - the8472:forget-envlock-on-fork, r=joshtriplett
Do not attempt to unlock envlock in child process after a fork.

This implements the first two points from https://github.com/rust-lang/rust/issues/64718#issuecomment-793030479

This is a breaking change for cases where the environment is accessed in a Command::pre_exec closure. Except for single-threaded programs these uses were not correct anyway since they aren't async-signal safe.

Note that we had a ui test that explicitly tried `env::set_var` in `pre_exec`. As expected it failed with these changes when I tested locally.
2021-03-10 17:55:43 +01:00
Yuki Okushi
74e74e9df8
Rollup merge of #82411 - ijackson:fix-exitstatus, r=dtolnay
Fixes to ExitStatus and its docs

* On Unix, properly display every possible wait status (and don't panic on weird values)
* In the documentation, be clear and consistent about "exit status" vs "wait status".
2021-03-10 08:01:27 +09:00
The8472
d854789ce1 Do not attempt to unlock envlock in child process after a fork.
This is a breaking change for cases where the environment is
accessed in a Command::pre_exec closure. Except for
single-threaded programs these uses were not correct
anyway since they aren't async-signal safe.
2021-03-09 22:14:07 +01:00
Eric Huss
acdca316c3 Revert "use RWlock when accessing os::env #81850"
This reverts commit 354f19cf24, reversing
changes made to 0cfba2fd09.
2021-03-07 11:32:42 -08:00
Ian Jackson
d8cfd56985 process::unix: Test wait status formatting
Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
2021-02-23 00:58:10 +00:00
Ian Jackson
fbd575aedf process::unix: Handle other wait statuses in ExitStatus as Display
Currently, on Nightly, this panics:

```
use std::process::ExitStatus;
use std::os::unix::process::ExitStatusExt;

fn main() {
    let st = ExitStatus::from_raw(0x007f);
    println!("st = {}", st);
}
```

This is because the impl of Display assumes that if .code() is None,
.signal() must be Some.  That was a false assumption, although it was
true with buggy code before
  5b1316f781
  unix ExitStatus: Do not treat WIFSTOPPED as WIFSIGNALED

This is not likely to have affected many people in practice, because
`Command` will never produce such a wait status (`ExitStatus`).

Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
2021-02-22 18:15:42 +00:00
The8472
44abad5b12 introduce StaticRWLock wrapper to make methods safe 2021-02-08 23:35:02 +01:00
The8472
55ca27faa7 use rwlock for accessing ENV 2021-02-07 09:12:21 +01:00
slo1
41e6b23000 Add setgroups to std::os::unix::process::CommandExt 2021-01-21 22:42:38 -08:00
Eric Huss
a938725ef7 Don't use posix_spawn_file_actions_addchdir_np on macOS. 2021-01-17 09:51:02 -08:00
Ian Jackson
42ea8f6434 unix ExitStatus: Provide .continued()
Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
2021-01-13 12:50:29 +00:00
Ian Jackson
f060b9e0d9 unix ExitStatus: Provide .stopped_signal()
Necessary to handle WIFSTOPPED.

Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
2021-01-13 12:50:29 +00:00
Ian Jackson
3f05051d6b unix ExitStatus: Provide .core_dumped
This is essential for proper reporting of child process status on Unix.

Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
2021-01-13 12:50:29 +00:00
Ian Jackson
530270f94a unix ExitStatus: Provide .into_raw()
Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
2021-01-13 12:50:29 +00:00
Ian Jackson
5b1316f781 unix ExitStatus: Do not treat WIFSTOPPED as WIFSIGNALED
A unix wait status can contain, at least, exit statuses, termination
signals, and stop signals.

WTERMSIG is only valid if WIFSIGNALED.

https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html

It will not be easy to experience this bug with `Command`, because
that doesn't pass WUNTRACED.  But you could make an ExitStatus
containing, say, a WIFSTOPPED, from a call to one of the libc wait
functions.

(In the WIFSTOPPED case, there is WSTOPSIG.  But a stop signal is
encoded differently to a termination signal, so WTERMSIG and WSTOPSIG
are by no means the same.)

Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
2021-01-13 12:50:29 +00:00
Tomasz Miąsko
21c29b1e95 Check that pthread mutex initialization succeeded
If pthread mutex initialization fails, the failure will go unnoticed unless
debug assertions are enabled. Any subsequent use of mutex will also silently
fail, since return values from lock & unlock operations are similarly checked
only through debug assertions.

In some implementations the mutex initialization requires a memory
allocation and so it does fail in practice.

Check that initialization succeeds to ensure that mutex guarantees
mutual exclusion.
2020-10-20 00:00:00 +00:00
bors
4cb7ef0f94 Auto merge of #77455 - asm89:faster-spawn, r=kennytm
Use posix_spawn() on unix if program is a path

Previously `Command::spawn` would fall back to the non-posix_spawn based
implementation if the `PATH` environment variable was possibly changed.
On systems with a modern (g)libc `posix_spawn()` can be significantly
faster. If program is a path itself the `PATH` environment variable is
not used for the lookup and it should be safe to use the
`posix_spawnp()` method. [1]

We found this, because we have a cli application that effectively runs a
lot of subprocesses. It would sometimes noticeably hang while printing
output. Profiling showed that the process was spending the majority of
time in the kernel's `copy_page_range` function while spawning
subprocesses. During this time the process is completely blocked from
running, explaining why users were reporting the cli app hanging.

Through this we discovered that `std::process::Command` has a fast and
slow path for process execution. The fast path is backed by
`posix_spawnp()` and the slow path by fork/exec syscalls being called
explicitly. Using fork for process creation is supposed to be fast, but
it slows down as your process uses more memory.  It's not because the
kernel copies the actual memory from the parent, but it does need to
copy the references to it (see `copy_page_range` above!).  We ended up
using the slow path, because the command spawn implementation in falls
back to the slow path if it suspects the PATH environment variable was
changed.

Here is a smallish program demonstrating the slowdown before this code
change:

```
use std::process::Command;
use std::time::Instant;

fn main() {
    let mut args = std::env::args().skip(1);
    if let Some(size) = args.next() {
        // Allocate some memory
        let _xs: Vec<_> = std::iter::repeat(0)
            .take(size.parse().expect("valid number"))
            .collect();

        let mut command = Command::new("/bin/sh");
        command
            .arg("-c")
            .arg("echo hello");

        if args.next().is_some() {
            println!("Overriding PATH");
            command.env("PATH", std::env::var("PATH").expect("PATH env var"));
        }

        let now = Instant::now();
        let child = command
            .spawn()
            .expect("failed to execute process");

        println!("Spawn took: {:?}", now.elapsed());

        let output = child.wait_with_output().expect("failed to wait on process");
        println!("Output: {:?}", output);
    } else {
        eprintln!("Usage: prog [size]");
        std::process::exit(1);
    }
    ()
}
```

Running it and passing different amounts of elements to use to allocate
memory shows that the time taken for `spawn()` can differ quite
significantly. In latter case the `posix_spawnp()` implementation is 30x
faster:

```
$ cargo run --release 10000000
...
Spawn took: 324.275µs
hello
$ cargo run --release 10000000 changepath
...
Overriding PATH
Spawn took: 2.346809ms
hello
$ cargo run --release 100000000
...
Spawn took: 387.842µs
hello
$ cargo run --release 100000000 changepath
...
Overriding PATH
Spawn took: 13.434677ms
hello
```

[1]: 5f72f9800b/posix/execvpe.c (L81)
2020-10-17 06:16:00 +00:00
Mara Bos
0f0257be10 Take some of sys/vxworks/process/* from sys/unix instead. 2020-10-16 06:22:05 +02:00
Tomasz Miąsko
6cd5506897 Check for errors returned from posix_spawn*_init functions
The posix_spawnattr_init & posix_spawn_file_actions_init might fail,
but their return code is not checked.

Check for non-zero return code and destroy only succesfully initialized
objects.
2020-10-08 23:53:15 +02:00
Tomasz Miąsko
5faf25b95c Check for non-zero return value from posix_spawn functions
The cvt function compares the argument with -1 and when equal returns a new
io::Error constructed from errno. It is used together posix_spawn_* functions.
This is incorrect. Those functions do not set errno. Instead they return
non-zero error code directly.

Check for non-zero return code and use it to construct a new io::Error.
2020-10-08 23:53:15 +02:00
Josh Triplett
16ebf750cf Update libc to 0.2.79
This also fixes issues with inconsistent `unsafe` on functions.
2020-10-04 22:12:07 -07:00
Jonas Schievink
72d275d844
Rollup merge of #77432 - tmiasko:posix-spawn-musl, r=cuviper
Use posix_spawn on musl targets

The posix_spawn had been available in a form suitable for use in a
Command implementation since musl 0.9.12. Use it in a preference to a
fork when possible, to benefit from CLONE_VM|CLONE_VFORK used there.
2020-10-02 20:27:11 +02:00
Alexander Mols
8fe6154669 Use posix_spawn() on unix if program is a path
Previously `Command::spawn` would fall back to the non-posix_spawn based
implementation if the `PATH` environment variable was possibly changed.
On systems with a modern (g)libc `posix_spawn()` can be significantly
faster. If program is a path itself the `PATH` environment variable is
not used for the lookup and it should be safe to use the
`posix_spawnp()` method. [1]

We found this, because we have a cli application that effectively runs a
lot of subprocesses. It would sometimes noticeably hang while printing
output. Profiling showed that the process was spending the majority of
time in the kernel's `copy_page_range` function while spawning
subprocesses. During this time the process is completely blocked from
running, explaining why users were reporting the cli app hanging.

Through this we discovered that `std::process::Command` has a fast and
slow path for process execution. The fast path is backed by
`posix_spawnp()` and the slow path by fork/exec syscalls being called
explicitly. Using fork for process creation is supposed to be fast, but
it slows down as your process uses more memory.  It's not because the
kernel copies the actual memory from the parent, but it does need to
copy the references to it (see `copy_page_range` above!).  We ended up
using the slow path, because the command spawn implementation in falls
back to the slow path if it suspects the PATH environment variable was
changed.

Here is a smallish program demonstrating the slowdown before this code
change:

```
use std::process::Command;
use std::time::Instant;

fn main() {
    let mut args = std::env::args().skip(1);
    if let Some(size) = args.next() {
        // Allocate some memory
        let _xs: Vec<_> = std::iter::repeat(0)
            .take(size.parse().expect("valid number"))
            .collect();

        let mut command = Command::new("/bin/sh");
        command
            .arg("-c")
            .arg("echo hello");

        if args.next().is_some() {
            println!("Overriding PATH");
            command.env("PATH", std::env::var("PATH").expect("PATH env var"));
        }

        let now = Instant::now();
        let child = command
            .spawn()
            .expect("failed to execute process");

        println!("Spawn took: {:?}", now.elapsed());

        let output = child.wait_with_output().expect("failed to wait on process");
        println!("Output: {:?}", output);
    } else {
        eprintln!("Usage: prog [size]");
        std::process::exit(1);
    }
    ()
}
```

Running it and passing different amounts of elements to use to allocate
memory shows that the time taken for `spawn()` can differ quite
significantly. In latter case the `posix_spawnp()` implementation is 30x
faster:

```
$ cargo run --release 10000000
...
Spawn took: 324.275µs
hello
$ cargo run --release 10000000 changepath
...
Overriding PATH
Spawn took: 2.346809ms
hello
$ cargo run --release 100000000
...
Spawn took: 387.842µs
hello
$ cargo run --release 100000000 changepath
...
Overriding PATH
Spawn took: 13.434677ms
hello
```

[1]: 5f72f9800b/posix/execvpe.c (L81)
2020-10-02 11:11:00 -07:00
bors
154f1f544d Auto merge of #77029 - ehuss:command-access, r=dtolnay
Add accessors to Command.

This adds some accessor methods to `Command` to provide a way to access the values set when building the `Command`. An example where this can be useful is to display the command to be executed. This is roughly based on the [`ProcessBuilder`](13b73cdaf7/src/cargo/util/process_builder.rs (L105-L134)) in Cargo.

Possible concerns about the API:
- Values with NULs on Unix will be returned as `"<string-with-nul>"`. I don't think it is practical to avoid this, since otherwise a whole separate copy of all the values would need to be kept in `Command`.
- Does not handle `arg0` on Unix. This can be awkward to support in `get_args` and is rarely used. I figure if someone really wants it, it can be added to `CommandExt` as a separate method.
- Does not offer a way to detect `env_clear`. I'm uncertain if it would be useful for anyone.
- Does not offer a way to get an environment variable by name (`get_env`). I figure this can be added later if anyone really wants it. I think the motivation for this is weak, though. Also, the API could be a little awkward (return a `Option<Option<&OsStr>>`?).
- `get_envs` could skip "cleared" entries and just return `&OsStr` values instead of `Option<&OsStr>`. I'm on the fence here. My use case is to display a shell command, and I only intend it to be roughly equivalent to the actual execution, and I probably won't display `None` entries. I erred on the side of providing extra information, but I suspect many situations will just filter out the `None`s.
- Could implement more iterator stuff (like `DoubleEndedIterator`).

I have not implemented new std items before, so I'm uncertain if the existing issue should be reused, or if a new tracking issue is needed.

cc #44434
2020-10-02 07:51:24 +00:00
Tomasz Miąsko
9845e7d5fb Use posix_spawn on musl targets
The posix_spawn had been available in a form suitable for use in a
Command implementation since musl 0.9.12. Use it in a preference to a
fork when possible, to benefit from CLONE_VM|CLONE_VFORK used there.
2020-10-01 00:00:00 +00:00
hyd-dev
a2526b416f
Use rtassert! instead of assert! from the child process after fork() in std::sys::unix::process::Command::spawn()
`assert!` panics on failure, which is not signal-safe.
2020-09-29 15:16:46 +08:00
Eric Huss
c297e20e03 Add accessors to Command. 2020-09-26 18:58:38 -07:00
Thomas de Zeeuw
c394624471 Ignore unnecessary unsafe warnings
This is a work-around for a libc issue:
https://github.com/rust-lang/libc/issues/1888.
2020-09-11 19:12:06 +02:00
mark
2c31b45ae8 mv std libs to library/ 2020-07-27 19:51:13 -05:00