Skip to content

Commit c327977

Browse files
committed
Auto merge of #150716 - matthiaskrgr:rollup-6l4olkp, r=matthiaskrgr
Rollup of 3 pull requests Successful merges: - #150412 (use PIDFD_GET_INFO ioctl when available) - #150668 (Unix implementation for stdio set/take/replace) - #150702 (`rust-analyzer` subtree update) r? `@ghost` `@rustbot` modify labels: rollup
2 parents 4fa80a5 + 741489f commit c327977

65 files changed

Lines changed: 1021 additions & 423 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

library/std/src/os/linux/process.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,10 @@ impl PidFd {
6767
/// Waits for the child to exit completely, returning the status that it exited with.
6868
///
6969
/// Unlike [`Child::wait`] it does not ensure that the stdin handle is closed.
70-
/// Additionally it will not return an `ExitStatus` if the child
71-
/// has already been reaped. Instead an error will be returned.
70+
///
71+
/// Additionally on kernels prior to 6.15 only the first attempt to
72+
/// reap a child will return an ExitStatus, further attempts
73+
/// will return an Error.
7274
///
7375
/// [`Child::wait`]: process::Child::wait
7476
pub fn wait(&self) -> Result<ExitStatus> {
@@ -77,8 +79,8 @@ impl PidFd {
7779

7880
/// Attempts to collect the exit status of the child if it has already exited.
7981
///
80-
/// Unlike [`Child::try_wait`] this method will return an Error
81-
/// if the child has already been reaped.
82+
/// On kernels prior to 6.15, and unlike [`Child::try_wait`], only the first attempt
83+
/// to reap a child will return an ExitStatus, further attempts will return an Error.
8284
///
8385
/// [`Child::try_wait`]: process::Child::try_wait
8486
pub fn try_wait(&self) -> Result<Option<ExitStatus>> {

library/std/src/os/unix/io/mod.rs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,137 @@
9292
9393
#![stable(feature = "rust1", since = "1.0.0")]
9494

95+
use crate::io::{self, Stderr, StderrLock, Stdin, StdinLock, Stdout, StdoutLock, Write};
9596
#[stable(feature = "rust1", since = "1.0.0")]
9697
pub use crate::os::fd::*;
98+
#[allow(unused_imports)] // not used on all targets
99+
use crate::sys::cvt;
97100

98101
// Tests for this module
99102
#[cfg(test)]
100103
mod tests;
104+
105+
#[unstable(feature = "stdio_swap", issue = "150667", reason = "recently added")]
106+
pub trait StdioExt: crate::sealed::Sealed {
107+
/// Redirects the stdio file descriptor to point to the file description underpinning `fd`.
108+
///
109+
/// Rust std::io write buffers (if any) are flushed, but other runtimes
110+
/// (e.g. C stdio) or libraries that acquire a clone of the file descriptor
111+
/// will not be aware of this change.
112+
///
113+
/// # Platform-specific behavior
114+
///
115+
/// This is [currently] implemented using
116+
///
117+
/// - `fd_renumber` on wasip1
118+
/// - `dup2` on most unixes
119+
///
120+
/// [currently]: crate::io#platform-specific-behavior
121+
///
122+
/// ```
123+
/// #![feature(stdio_swap)]
124+
/// use std::io::{self, Read, Write};
125+
/// use std::os::unix::io::StdioExt;
126+
///
127+
/// fn main() -> io::Result<()> {
128+
/// let (reader, mut writer) = io::pipe()?;
129+
/// let mut stdin = io::stdin();
130+
/// stdin.set_fd(reader)?;
131+
/// writer.write_all(b"Hello, world!")?;
132+
/// let mut buffer = vec![0; 13];
133+
/// assert_eq!(stdin.read(&mut buffer)?, 13);
134+
/// assert_eq!(&buffer, b"Hello, world!");
135+
/// Ok(())
136+
/// }
137+
/// ```
138+
fn set_fd<T: Into<OwnedFd>>(&mut self, fd: T) -> io::Result<()>;
139+
140+
/// Redirects the stdio file descriptor and returns a new `OwnedFd`
141+
/// backed by the previous file description.
142+
///
143+
/// See [`set_fd()`] for details.
144+
///
145+
/// [`set_fd()`]: StdioExt::set_fd
146+
fn replace_fd<T: Into<OwnedFd>>(&mut self, replace_with: T) -> io::Result<OwnedFd>;
147+
148+
/// Redirects the stdio file descriptor to the null device (`/dev/null`)
149+
/// and returns a new `OwnedFd` backed by the previous file description.
150+
///
151+
/// Programs that communicate structured data via stdio can use this early in `main()` to
152+
/// extract the fds, treat them as other IO types (`File`, `UnixStream`, etc),
153+
/// apply custom buffering or avoid interference from stdio use later in the program.
154+
///
155+
/// See [`set_fd()`] for additional details.
156+
///
157+
/// [`set_fd()`]: StdioExt::set_fd
158+
fn take_fd(&mut self) -> io::Result<OwnedFd>;
159+
}
160+
161+
macro io_ext_impl($stdio_ty:ty, $stdio_lock_ty:ty, $writer:literal) {
162+
#[unstable(feature = "stdio_swap", issue = "150667", reason = "recently added")]
163+
impl StdioExt for $stdio_ty {
164+
fn set_fd<T: Into<OwnedFd>>(&mut self, fd: T) -> io::Result<()> {
165+
self.lock().set_fd(fd)
166+
}
167+
168+
fn take_fd(&mut self) -> io::Result<OwnedFd> {
169+
self.lock().take_fd()
170+
}
171+
172+
fn replace_fd<T: Into<OwnedFd>>(&mut self, replace_with: T) -> io::Result<OwnedFd> {
173+
self.lock().replace_fd(replace_with)
174+
}
175+
}
176+
177+
#[unstable(feature = "stdio_swap", issue = "150667", reason = "recently added")]
178+
impl StdioExt for $stdio_lock_ty {
179+
fn set_fd<T: Into<OwnedFd>>(&mut self, fd: T) -> io::Result<()> {
180+
#[cfg($writer)]
181+
self.flush()?;
182+
replace_stdio_fd(self.as_fd(), fd.into())
183+
}
184+
185+
fn take_fd(&mut self) -> io::Result<OwnedFd> {
186+
let null = null_fd()?;
187+
let cloned = self.as_fd().try_clone_to_owned()?;
188+
self.set_fd(null)?;
189+
Ok(cloned)
190+
}
191+
192+
fn replace_fd<T: Into<OwnedFd>>(&mut self, replace_with: T) -> io::Result<OwnedFd> {
193+
let cloned = self.as_fd().try_clone_to_owned()?;
194+
self.set_fd(replace_with)?;
195+
Ok(cloned)
196+
}
197+
}
198+
}
199+
200+
io_ext_impl!(Stdout, StdoutLock<'_>, true);
201+
io_ext_impl!(Stdin, StdinLock<'_>, false);
202+
io_ext_impl!(Stderr, StderrLock<'_>, true);
203+
204+
fn null_fd() -> io::Result<OwnedFd> {
205+
let null_dev = crate::fs::OpenOptions::new().read(true).write(true).open("/dev/null")?;
206+
Ok(null_dev.into())
207+
}
208+
209+
/// Replaces the underlying file descriptor with the one from `other`.
210+
/// Does not set CLOEXEC.
211+
fn replace_stdio_fd(this: BorrowedFd<'_>, other: OwnedFd) -> io::Result<()> {
212+
cfg_select! {
213+
all(target_os = "wasi", target_env = "p1") => {
214+
cvt(unsafe { libc::__wasilibc_fd_renumber(other.as_raw_fd(), this.as_raw_fd()) }).map(|_| ())
215+
}
216+
not(any(
217+
target_arch = "wasm32",
218+
target_os = "hermit",
219+
target_os = "trusty",
220+
target_os = "motor"
221+
)) => {
222+
cvt(unsafe {libc::dup2(other.as_raw_fd(), this.as_raw_fd())}).map(|_| ())
223+
}
224+
_ => {
225+
Err(io::Error::UNSUPPORTED_PLATFORM)
226+
}
227+
}
228+
}

library/std/src/sys/pal/unix/linux/pidfd.rs

Lines changed: 77 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use crate::io;
2-
use crate::os::fd::{AsRawFd, FromRawFd, RawFd};
2+
use crate::os::fd::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
33
use crate::sys::fd::FileDesc;
44
use crate::sys::process::ExitStatus;
5+
use crate::sys::weak::weak;
56
use crate::sys::{AsInner, FromInner, IntoInner, cvt};
67

78
#[cfg(test)]
@@ -15,6 +16,69 @@ impl PidFd {
1516
self.send_signal(libc::SIGKILL)
1617
}
1718

19+
pub(crate) fn current_process() -> io::Result<PidFd> {
20+
let pid = crate::process::id();
21+
let pidfd = cvt(unsafe { libc::syscall(libc::SYS_pidfd_open, pid, 0) })?;
22+
Ok(unsafe { PidFd::from_raw_fd(pidfd as RawFd) })
23+
}
24+
25+
pub(crate) fn pid(&self) -> io::Result<u32> {
26+
// since kernel 6.13
27+
// https://lore.kernel.org/all/20241010155401.2268522-1-luca.boccassi@gmail.com/
28+
let mut pidfd_info: libc::pidfd_info = unsafe { crate::mem::zeroed() };
29+
pidfd_info.mask = libc::PIDFD_INFO_PID as u64;
30+
match cvt(unsafe { libc::ioctl(self.0.as_raw_fd(), libc::PIDFD_GET_INFO, &mut pidfd_info) })
31+
{
32+
Ok(_) => {}
33+
Err(e) if e.raw_os_error() == Some(libc::EINVAL) => {
34+
// kernel doesn't support that ioctl, try the glibc helper that looks at procfs
35+
weak!(
36+
fn pidfd_getpid(pidfd: RawFd) -> libc::pid_t;
37+
);
38+
if let Some(pidfd_getpid) = pidfd_getpid.get() {
39+
let pid: libc::c_int = cvt(unsafe { pidfd_getpid(self.0.as_raw_fd()) })?;
40+
return Ok(pid as u32);
41+
}
42+
return Err(e);
43+
}
44+
Err(e) => return Err(e),
45+
}
46+
47+
Ok(pidfd_info.pid)
48+
}
49+
50+
fn exit_for_reaped_child(&self) -> io::Result<ExitStatus> {
51+
// since kernel 6.15
52+
// https://lore.kernel.org/linux-fsdevel/20250305-work-pidfs-kill_on_last_close-v3-0-c8c3d8361705@kernel.org/T/
53+
let mut pidfd_info: libc::pidfd_info = unsafe { crate::mem::zeroed() };
54+
pidfd_info.mask = libc::PIDFD_INFO_EXIT as u64;
55+
cvt(unsafe { libc::ioctl(self.0.as_raw_fd(), libc::PIDFD_GET_INFO, &mut pidfd_info) })?;
56+
Ok(ExitStatus::new(pidfd_info.exit_code))
57+
}
58+
59+
fn waitid(&self, options: libc::c_int) -> io::Result<Option<ExitStatus>> {
60+
let mut siginfo: libc::siginfo_t = unsafe { crate::mem::zeroed() };
61+
let r = cvt(unsafe {
62+
libc::waitid(libc::P_PIDFD, self.0.as_raw_fd() as u32, &mut siginfo, options)
63+
});
64+
match r {
65+
Err(waitid_err) if waitid_err.raw_os_error() == Some(libc::ECHILD) => {
66+
// already reaped
67+
match self.exit_for_reaped_child() {
68+
Ok(exit_status) => return Ok(Some(exit_status)),
69+
Err(_) => return Err(waitid_err),
70+
}
71+
}
72+
Err(e) => return Err(e),
73+
Ok(_) => {}
74+
}
75+
if unsafe { siginfo.si_pid() } == 0 {
76+
Ok(None)
77+
} else {
78+
Ok(Some(ExitStatus::from_waitid_siginfo(siginfo)))
79+
}
80+
}
81+
1882
pub(crate) fn send_signal(&self, signal: i32) -> io::Result<()> {
1983
cvt(unsafe {
2084
libc::syscall(
@@ -29,29 +93,15 @@ impl PidFd {
2993
}
3094

3195
pub fn wait(&self) -> io::Result<ExitStatus> {
32-
let mut siginfo: libc::siginfo_t = unsafe { crate::mem::zeroed() };
33-
cvt(unsafe {
34-
libc::waitid(libc::P_PIDFD, self.0.as_raw_fd() as u32, &mut siginfo, libc::WEXITED)
35-
})?;
36-
Ok(ExitStatus::from_waitid_siginfo(siginfo))
96+
let r = self.waitid(libc::WEXITED)?;
97+
match r {
98+
Some(exit_status) => Ok(exit_status),
99+
None => unreachable!("waitid with WEXITED should not return None"),
100+
}
37101
}
38102

39103
pub fn try_wait(&self) -> io::Result<Option<ExitStatus>> {
40-
let mut siginfo: libc::siginfo_t = unsafe { crate::mem::zeroed() };
41-
42-
cvt(unsafe {
43-
libc::waitid(
44-
libc::P_PIDFD,
45-
self.0.as_raw_fd() as u32,
46-
&mut siginfo,
47-
libc::WEXITED | libc::WNOHANG,
48-
)
49-
})?;
50-
if unsafe { siginfo.si_pid() } == 0 {
51-
Ok(None)
52-
} else {
53-
Ok(Some(ExitStatus::from_waitid_siginfo(siginfo)))
54-
}
104+
self.waitid(libc::WEXITED | libc::WNOHANG)
55105
}
56106
}
57107

@@ -78,3 +128,9 @@ impl FromRawFd for PidFd {
78128
Self(FileDesc::from_raw_fd(fd))
79129
}
80130
}
131+
132+
impl IntoRawFd for PidFd {
133+
fn into_raw_fd(self) -> RawFd {
134+
self.0.into_raw_fd()
135+
}
136+
}

library/std/src/sys/pal/unix/linux/pidfd/tests.rs

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
use super::PidFd as InternalPidFd;
12
use crate::assert_matches::assert_matches;
2-
use crate::os::fd::{AsRawFd, RawFd};
3+
use crate::io::ErrorKind;
4+
use crate::os::fd::AsRawFd;
35
use crate::os::linux::process::{ChildExt, CommandExt as _};
46
use crate::os::unix::process::{CommandExt as _, ExitStatusExt};
57
use crate::process::Command;
8+
use crate::sys::AsInner;
69

710
#[test]
811
fn test_command_pidfd() {
@@ -48,11 +51,22 @@ fn test_command_pidfd() {
4851
let mut cmd = Command::new("false");
4952
let mut child = unsafe { cmd.pre_exec(|| Ok(())) }.create_pidfd(true).spawn().unwrap();
5053

51-
assert!(child.id() > 0 && child.id() < -1i32 as u32);
54+
let id = child.id();
55+
56+
assert!(id > 0 && id < -1i32 as u32, "spawning with pidfd still returns a sane pid");
5257

5358
if pidfd_open_available {
5459
assert!(child.pidfd().is_ok())
5560
}
61+
62+
if let Ok(pidfd) = child.pidfd() {
63+
match pidfd.as_inner().pid() {
64+
Ok(pid) => assert_eq!(pid, id),
65+
Err(e) if e.kind() == ErrorKind::InvalidInput => { /* older kernel */ }
66+
Err(e) => panic!("unexpected error getting pid from pidfd: {}", e),
67+
}
68+
}
69+
5670
child.wait().expect("error waiting on child");
5771
}
5872

@@ -77,23 +91,21 @@ fn test_pidfd() {
7791
assert_eq!(status.signal(), Some(libc::SIGKILL));
7892

7993
// Trying to wait again for a reaped child is safe since there's no pid-recycling race.
80-
// But doing so will return an error.
94+
// But doing so may return an error.
8195
let res = fd.wait();
82-
assert_matches!(res, Err(e) if e.raw_os_error() == Some(libc::ECHILD));
96+
match res {
97+
// older kernels
98+
Err(e) if e.raw_os_error() == Some(libc::ECHILD) => {}
99+
// 6.15+
100+
Ok(exit) if exit.signal() == Some(libc::SIGKILL) => {}
101+
other => panic!("expected ECHILD error, got {:?}", other),
102+
}
83103

84104
// Ditto for additional attempts to kill an already-dead child.
85105
let res = fd.kill();
86106
assert_matches!(res, Err(e) if e.raw_os_error() == Some(libc::ESRCH));
87107
}
88108

89109
fn probe_pidfd_support() -> bool {
90-
// pidfds require the pidfd_open syscall
91-
let our_pid = crate::process::id();
92-
let pidfd = unsafe { libc::syscall(libc::SYS_pidfd_open, our_pid, 0) };
93-
if pidfd >= 0 {
94-
unsafe { libc::close(pidfd as RawFd) };
95-
true
96-
} else {
97-
false
98-
}
110+
InternalPidFd::current_process().is_ok()
99111
}

0 commit comments

Comments
 (0)