-
Notifications
You must be signed in to change notification settings - Fork 12.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Auto merge of #124101 - the8472:pidfd-methods, r=cuviper
Add PidFd::{kill, wait, try_wait} #117957 changed `Child` kill/wait/try_wait to use its pidfd instead of the pid, when one is available. This PR extracts those implementations and makes them available on `PidFd` directly. The `PidFd` implementations differ significantly from the corresponding `Child` methods: * the methods can be called after the child has been reaped, which will result in an error but will be safe. This state is not observable in `Child` unless something stole the zombie child * the `ExitStatus` is not kept, meaning that only the first time a wait succeeds it will be returned * `wait` does not close stdin * `wait` only requires `&self` instead of `&mut self` since there is no state to maintain and subsequent calls are safe Tracking issue: #82971
- Loading branch information
Showing
7 changed files
with
265 additions
and
124 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
pub mod pidfd; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
use crate::io; | ||
use crate::os::fd::{AsRawFd, FromRawFd, RawFd}; | ||
use crate::sys::cvt; | ||
use crate::sys::pal::unix::fd::FileDesc; | ||
use crate::sys::process::ExitStatus; | ||
use crate::sys_common::{AsInner, FromInner, IntoInner}; | ||
|
||
#[cfg(test)] | ||
mod tests; | ||
|
||
#[derive(Debug)] | ||
pub(crate) struct PidFd(FileDesc); | ||
|
||
impl PidFd { | ||
pub fn kill(&self) -> io::Result<()> { | ||
return cvt(unsafe { | ||
libc::syscall( | ||
libc::SYS_pidfd_send_signal, | ||
self.0.as_raw_fd(), | ||
libc::SIGKILL, | ||
crate::ptr::null::<()>(), | ||
0, | ||
) | ||
}) | ||
.map(drop); | ||
} | ||
|
||
pub fn wait(&self) -> io::Result<ExitStatus> { | ||
let mut siginfo: libc::siginfo_t = unsafe { crate::mem::zeroed() }; | ||
cvt(unsafe { | ||
libc::waitid(libc::P_PIDFD, self.0.as_raw_fd() as u32, &mut siginfo, libc::WEXITED) | ||
})?; | ||
return Ok(ExitStatus::from_waitid_siginfo(siginfo)); | ||
} | ||
|
||
pub fn try_wait(&self) -> io::Result<Option<ExitStatus>> { | ||
let mut siginfo: libc::siginfo_t = unsafe { crate::mem::zeroed() }; | ||
|
||
cvt(unsafe { | ||
libc::waitid( | ||
libc::P_PIDFD, | ||
self.0.as_raw_fd() as u32, | ||
&mut siginfo, | ||
libc::WEXITED | libc::WNOHANG, | ||
) | ||
})?; | ||
if unsafe { siginfo.si_pid() } == 0 { | ||
return Ok(None); | ||
} | ||
return Ok(Some(ExitStatus::from_waitid_siginfo(siginfo))); | ||
} | ||
} | ||
|
||
impl AsInner<FileDesc> for PidFd { | ||
fn as_inner(&self) -> &FileDesc { | ||
&self.0 | ||
} | ||
} | ||
|
||
impl IntoInner<FileDesc> for PidFd { | ||
fn into_inner(self) -> FileDesc { | ||
self.0 | ||
} | ||
} | ||
|
||
impl FromInner<FileDesc> for PidFd { | ||
fn from_inner(inner: FileDesc) -> Self { | ||
Self(inner) | ||
} | ||
} | ||
|
||
impl FromRawFd for PidFd { | ||
unsafe fn from_raw_fd(fd: RawFd) -> Self { | ||
Self(FileDesc::from_raw_fd(fd)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
use crate::assert_matches::assert_matches; | ||
use crate::os::fd::{AsRawFd, RawFd}; | ||
use crate::os::linux::process::{ChildExt, CommandExt}; | ||
use crate::os::unix::process::ExitStatusExt; | ||
use crate::process::Command; | ||
|
||
#[test] | ||
fn test_command_pidfd() { | ||
let pidfd_open_available = probe_pidfd_support(); | ||
|
||
// always exercise creation attempts | ||
let mut child = Command::new("false").create_pidfd(true).spawn().unwrap(); | ||
|
||
// but only check if we know that the kernel supports pidfds. | ||
// We don't assert the precise value, since the standard library | ||
// might have opened other file descriptors before our code runs. | ||
if pidfd_open_available { | ||
assert!(child.pidfd().is_ok()); | ||
} | ||
if let Ok(pidfd) = child.pidfd() { | ||
let flags = super::cvt(unsafe { libc::fcntl(pidfd.as_raw_fd(), libc::F_GETFD) }).unwrap(); | ||
assert!(flags & libc::FD_CLOEXEC != 0); | ||
} | ||
let status = child.wait().expect("error waiting on pidfd"); | ||
assert_eq!(status.code(), Some(1)); | ||
|
||
let mut child = Command::new("sleep").arg("1000").create_pidfd(true).spawn().unwrap(); | ||
assert_matches!(child.try_wait(), Ok(None)); | ||
child.kill().expect("failed to kill child"); | ||
let status = child.wait().expect("error waiting on pidfd"); | ||
assert_eq!(status.signal(), Some(libc::SIGKILL)); | ||
|
||
let _ = Command::new("echo") | ||
.create_pidfd(false) | ||
.spawn() | ||
.unwrap() | ||
.pidfd() | ||
.expect_err("pidfd should not have been created when create_pid(false) is set"); | ||
|
||
let _ = Command::new("echo") | ||
.spawn() | ||
.unwrap() | ||
.pidfd() | ||
.expect_err("pidfd should not have been created"); | ||
} | ||
|
||
#[test] | ||
fn test_pidfd() { | ||
if !probe_pidfd_support() { | ||
return; | ||
} | ||
|
||
let child = Command::new("sleep") | ||
.arg("1000") | ||
.create_pidfd(true) | ||
.spawn() | ||
.expect("executing 'sleep' failed"); | ||
|
||
let fd = child.into_pidfd().unwrap(); | ||
|
||
assert_matches!(fd.try_wait(), Ok(None)); | ||
fd.kill().expect("kill failed"); | ||
fd.kill().expect("sending kill twice failed"); | ||
let status = fd.wait().expect("1st wait failed"); | ||
assert_eq!(status.signal(), Some(libc::SIGKILL)); | ||
|
||
// Trying to wait again for a reaped child is safe since there's no pid-recycling race. | ||
// But doing so will return an error. | ||
let res = fd.wait(); | ||
assert_matches!(res, Err(e) if e.raw_os_error() == Some(libc::ECHILD)); | ||
|
||
// Ditto for additional attempts to kill an already-dead child. | ||
let res = fd.kill(); | ||
assert_matches!(res, Err(e) if e.raw_os_error() == Some(libc::ESRCH)); | ||
} | ||
|
||
fn probe_pidfd_support() -> bool { | ||
// pidfds require the pidfd_open syscall | ||
let our_pid = crate::process::id(); | ||
let pidfd = unsafe { libc::syscall(libc::SYS_pidfd_open, our_pid, 0) }; | ||
if pidfd >= 0 { | ||
unsafe { libc::close(pidfd as RawFd) }; | ||
true | ||
} else { | ||
false | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.