Skip to content

Commit d01648b

Browse files
authored
Rollup merge of rust-lang#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 rust-lang#64718 (comment) 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.
2 parents 881bbb7 + d854789 commit d01648b

File tree

3 files changed

+19
-21
lines changed

3 files changed

+19
-21
lines changed

library/std/src/sys/unix/ext/process.rs

+10-1
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,14 @@ pub trait CommandExt: Sealed {
6262
/// `fork`. This primarily means that any modifications made to memory on
6363
/// behalf of this closure will **not** be visible to the parent process.
6464
/// This is often a very constrained environment where normal operations
65-
/// like `malloc` or acquiring a mutex are not guaranteed to work (due to
65+
/// like `malloc`, accessing environment variables through [`std::env`]
66+
/// or acquiring a mutex are not guaranteed to work (due to
6667
/// other threads perhaps still running when the `fork` was run).
6768
///
69+
/// For further details refer to the [POSIX fork() specification]
70+
/// and the equivalent documentation for any targeted
71+
/// platform, especially the requirements around *async-signal-safety*.
72+
///
6873
/// This also means that all resources such as file descriptors and
6974
/// memory-mapped regions got duplicated. It is your responsibility to make
7075
/// sure that the closure does not violate library invariants by making
@@ -73,6 +78,10 @@ pub trait CommandExt: Sealed {
7378
/// When this closure is run, aspects such as the stdio file descriptors and
7479
/// working directory have successfully been changed, so output to these
7580
/// locations may not appear where intended.
81+
///
82+
/// [POSIX fork() specification]:
83+
/// https://pubs.opengroup.org/onlinepubs/9699919799/functions/fork.html
84+
/// [`std::env`]: mod@crate::env
7685
#[stable(feature = "process_pre_exec", since = "1.34.0")]
7786
unsafe fn pre_exec<F>(&mut self, f: F) -> &mut process::Command
7887
where

library/std/src/sys/unix/process/process_unix.rs

+9-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::convert::TryInto;
22
use crate::fmt;
33
use crate::io::{self, Error, ErrorKind};
4+
use crate::mem;
45
use crate::ptr;
56
use crate::sys;
67
use crate::sys::cvt;
@@ -45,15 +46,14 @@ impl Command {
4546
//
4647
// Note that as soon as we're done with the fork there's no need to hold
4748
// a lock any more because the parent won't do anything and the child is
48-
// in its own process.
49-
let result = unsafe {
50-
let _env_lock = sys::os::env_lock();
51-
cvt(libc::fork())?
52-
};
49+
// in its own process. Thus the parent drops the lock guard while the child
50+
// forgets it to avoid unlocking it on a new thread, which would be invalid.
51+
let (env_lock, result) = unsafe { (sys::os::env_lock(), cvt(libc::fork())?) };
5352

5453
let pid = unsafe {
5554
match result {
5655
0 => {
56+
mem::forget(env_lock);
5757
drop(input);
5858
let Err(err) = self.do_exec(theirs, envp.as_ref());
5959
let errno = err.raw_os_error().unwrap_or(libc::EINVAL) as u32;
@@ -74,7 +74,10 @@ impl Command {
7474
rtassert!(output.write(&bytes).is_ok());
7575
libc::_exit(1)
7676
}
77-
n => n,
77+
n => {
78+
drop(env_lock);
79+
n
80+
}
7881
}
7982
};
8083

src/test/ui/command/command-pre-exec.rs

-14
Original file line numberDiff line numberDiff line change
@@ -43,20 +43,6 @@ fn main() {
4343
assert!(output.stderr.is_empty());
4444
assert_eq!(output.stdout, b"hello\nhello2\n");
4545

46-
let output = unsafe {
47-
Command::new(&me)
48-
.arg("test2")
49-
.pre_exec(|| {
50-
env::set_var("FOO", "BAR");
51-
Ok(())
52-
})
53-
.output()
54-
.unwrap()
55-
};
56-
assert!(output.status.success());
57-
assert!(output.stderr.is_empty());
58-
assert!(output.stdout.is_empty());
59-
6046
let output = unsafe {
6147
Command::new(&me)
6248
.arg("test3")

0 commit comments

Comments
 (0)