| 1 | #[cfg (target_os = "vxworks" )] |
| 2 | use libc::RTP_ID as pid_t; |
| 3 | #[cfg (not(target_os = "vxworks" ))] |
| 4 | use libc::{c_int, pid_t}; |
| 5 | #[cfg (not(any( |
| 6 | target_os = "vxworks" , |
| 7 | target_os = "l4re" , |
| 8 | target_os = "tvos" , |
| 9 | target_os = "watchos" , |
| 10 | )))] |
| 11 | use libc::{gid_t, uid_t}; |
| 12 | |
| 13 | use super::common::*; |
| 14 | use crate::io::{self, Error, ErrorKind}; |
| 15 | use crate::num::NonZero; |
| 16 | use crate::sys::cvt; |
| 17 | #[cfg (target_os = "linux" )] |
| 18 | use crate::sys::pal::linux::pidfd::PidFd; |
| 19 | use crate::{fmt, mem, sys}; |
| 20 | |
| 21 | cfg_if::cfg_if! { |
| 22 | if #[cfg(target_os = "nto" )] { |
| 23 | use crate::thread; |
| 24 | use libc::{c_char, posix_spawn_file_actions_t, posix_spawnattr_t}; |
| 25 | use crate::time::Duration; |
| 26 | use crate::sync::LazyLock; |
| 27 | // Get smallest amount of time we can sleep. |
| 28 | // Return a common value if it cannot be determined. |
| 29 | fn get_clock_resolution() -> Duration { |
| 30 | static MIN_DELAY: LazyLock<Duration, fn() -> Duration> = LazyLock::new(|| { |
| 31 | let mut mindelay = libc::timespec { tv_sec: 0, tv_nsec: 0 }; |
| 32 | if unsafe { libc::clock_getres(libc::CLOCK_MONOTONIC, &mut mindelay) } == 0 |
| 33 | { |
| 34 | Duration::from_nanos(mindelay.tv_nsec as u64) |
| 35 | } else { |
| 36 | Duration::from_millis(1) |
| 37 | } |
| 38 | }); |
| 39 | *MIN_DELAY |
| 40 | } |
| 41 | // Arbitrary minimum sleep duration for retrying fork/spawn |
| 42 | const MIN_FORKSPAWN_SLEEP: Duration = Duration::from_nanos(1); |
| 43 | // Maximum duration of sleeping before giving up and returning an error |
| 44 | const MAX_FORKSPAWN_SLEEP: Duration = Duration::from_millis(1000); |
| 45 | } |
| 46 | } |
| 47 | |
| 48 | //////////////////////////////////////////////////////////////////////////////// |
| 49 | // Command |
| 50 | //////////////////////////////////////////////////////////////////////////////// |
| 51 | |
| 52 | impl Command { |
| 53 | pub fn spawn( |
| 54 | &mut self, |
| 55 | default: Stdio, |
| 56 | needs_stdin: bool, |
| 57 | ) -> io::Result<(Process, StdioPipes)> { |
| 58 | const CLOEXEC_MSG_FOOTER: [u8; 4] = *b"NOEX" ; |
| 59 | |
| 60 | let envp = self.capture_env(); |
| 61 | |
| 62 | if self.saw_nul() { |
| 63 | return Err(io::const_error!( |
| 64 | ErrorKind::InvalidInput, |
| 65 | "nul byte found in provided data" , |
| 66 | )); |
| 67 | } |
| 68 | |
| 69 | let (ours, theirs) = self.setup_io(default, needs_stdin)?; |
| 70 | |
| 71 | if let Some(ret) = self.posix_spawn(&theirs, envp.as_ref())? { |
| 72 | return Ok((ret, ours)); |
| 73 | } |
| 74 | |
| 75 | #[cfg (target_os = "linux" )] |
| 76 | let (input, output) = sys::net::Socket::new_pair(libc::AF_UNIX, libc::SOCK_SEQPACKET)?; |
| 77 | |
| 78 | #[cfg (not(target_os = "linux" ))] |
| 79 | let (input, output) = sys::pipe::anon_pipe()?; |
| 80 | |
| 81 | // Whatever happens after the fork is almost for sure going to touch or |
| 82 | // look at the environment in one way or another (PATH in `execvp` or |
| 83 | // accessing the `environ` pointer ourselves). Make sure no other thread |
| 84 | // is accessing the environment when we do the fork itself. |
| 85 | // |
| 86 | // Note that as soon as we're done with the fork there's no need to hold |
| 87 | // a lock any more because the parent won't do anything and the child is |
| 88 | // in its own process. Thus the parent drops the lock guard immediately. |
| 89 | // The child calls `mem::forget` to leak the lock, which is crucial because |
| 90 | // releasing a lock is not async-signal-safe. |
| 91 | let env_lock = sys::env::env_read_lock(); |
| 92 | let pid = unsafe { self.do_fork()? }; |
| 93 | |
| 94 | if pid == 0 { |
| 95 | crate::panic::always_abort(); |
| 96 | mem::forget(env_lock); // avoid non-async-signal-safe unlocking |
| 97 | drop(input); |
| 98 | #[cfg (target_os = "linux" )] |
| 99 | if self.get_create_pidfd() { |
| 100 | self.send_pidfd(&output); |
| 101 | } |
| 102 | let Err(err) = unsafe { self.do_exec(theirs, envp.as_ref()) }; |
| 103 | let errno = err.raw_os_error().unwrap_or(libc::EINVAL) as u32; |
| 104 | let errno = errno.to_be_bytes(); |
| 105 | let bytes = [ |
| 106 | errno[0], |
| 107 | errno[1], |
| 108 | errno[2], |
| 109 | errno[3], |
| 110 | CLOEXEC_MSG_FOOTER[0], |
| 111 | CLOEXEC_MSG_FOOTER[1], |
| 112 | CLOEXEC_MSG_FOOTER[2], |
| 113 | CLOEXEC_MSG_FOOTER[3], |
| 114 | ]; |
| 115 | // pipe I/O up to PIPE_BUF bytes should be atomic, and then |
| 116 | // we want to be sure we *don't* run at_exit destructors as |
| 117 | // we're being torn down regardless |
| 118 | rtassert!(output.write(&bytes).is_ok()); |
| 119 | unsafe { libc::_exit(1) } |
| 120 | } |
| 121 | |
| 122 | drop(env_lock); |
| 123 | drop(output); |
| 124 | |
| 125 | #[cfg (target_os = "linux" )] |
| 126 | let pidfd = if self.get_create_pidfd() { self.recv_pidfd(&input) } else { -1 }; |
| 127 | |
| 128 | #[cfg (not(target_os = "linux" ))] |
| 129 | let pidfd = -1; |
| 130 | |
| 131 | // Safety: We obtained the pidfd (on Linux) using SOCK_SEQPACKET, so it's valid. |
| 132 | let mut p = unsafe { Process::new(pid, pidfd) }; |
| 133 | let mut bytes = [0; 8]; |
| 134 | |
| 135 | // loop to handle EINTR |
| 136 | loop { |
| 137 | match input.read(&mut bytes) { |
| 138 | Ok(0) => return Ok((p, ours)), |
| 139 | Ok(8) => { |
| 140 | let (errno, footer) = bytes.split_at(4); |
| 141 | assert_eq!( |
| 142 | CLOEXEC_MSG_FOOTER, footer, |
| 143 | "Validation on the CLOEXEC pipe failed: {:?}" , |
| 144 | bytes |
| 145 | ); |
| 146 | let errno = i32::from_be_bytes(errno.try_into().unwrap()); |
| 147 | assert!(p.wait().is_ok(), "wait() should either return Ok or panic" ); |
| 148 | return Err(Error::from_raw_os_error(errno)); |
| 149 | } |
| 150 | Err(ref e) if e.is_interrupted() => {} |
| 151 | Err(e) => { |
| 152 | assert!(p.wait().is_ok(), "wait() should either return Ok or panic" ); |
| 153 | panic!("the CLOEXEC pipe failed: {e:?}" ) |
| 154 | } |
| 155 | Ok(..) => { |
| 156 | // pipe I/O up to PIPE_BUF bytes should be atomic |
| 157 | // similarly SOCK_SEQPACKET messages should arrive whole |
| 158 | assert!(p.wait().is_ok(), "wait() should either return Ok or panic" ); |
| 159 | panic!("short read on the CLOEXEC pipe" ) |
| 160 | } |
| 161 | } |
| 162 | } |
| 163 | } |
| 164 | |
| 165 | // WatchOS and TVOS headers mark the `fork`/`exec*` functions with |
| 166 | // `__WATCHOS_PROHIBITED __TVOS_PROHIBITED`, and indicate that the |
| 167 | // `posix_spawn*` functions should be used instead. It isn't entirely clear |
| 168 | // what `PROHIBITED` means here (e.g. if calls to these functions are |
| 169 | // allowed to exist in dead code), but it sounds bad, so we go out of our |
| 170 | // way to avoid that all-together. |
| 171 | #[cfg (any(target_os = "tvos" , target_os = "watchos" ))] |
| 172 | const ERR_APPLE_TV_WATCH_NO_FORK_EXEC: Error = io::const_error!( |
| 173 | ErrorKind::Unsupported, |
| 174 | "`fork`+`exec`-based process spawning is not supported on this target" , |
| 175 | ); |
| 176 | |
| 177 | #[cfg (any(target_os = "tvos" , target_os = "watchos" ))] |
| 178 | unsafe fn do_fork(&mut self) -> Result<pid_t, io::Error> { |
| 179 | return Err(Self::ERR_APPLE_TV_WATCH_NO_FORK_EXEC); |
| 180 | } |
| 181 | |
| 182 | // Attempts to fork the process. If successful, returns Ok((0, -1)) |
| 183 | // in the child, and Ok((child_pid, -1)) in the parent. |
| 184 | #[cfg (not(any(target_os = "watchos" , target_os = "tvos" , target_os = "nto" )))] |
| 185 | unsafe fn do_fork(&mut self) -> Result<pid_t, io::Error> { |
| 186 | cvt(libc::fork()) |
| 187 | } |
| 188 | |
| 189 | // On QNX Neutrino, fork can fail with EBADF in case "another thread might have opened |
| 190 | // or closed a file descriptor while the fork() was occurring". |
| 191 | // Documentation says "... or try calling fork() again". This is what we do here. |
| 192 | // See also https://www.qnx.com/developers/docs/7.1/#com.qnx.doc.neutrino.lib_ref/topic/f/fork.html |
| 193 | #[cfg (target_os = "nto" )] |
| 194 | unsafe fn do_fork(&mut self) -> Result<pid_t, io::Error> { |
| 195 | use crate::sys::os::errno; |
| 196 | |
| 197 | let mut delay = MIN_FORKSPAWN_SLEEP; |
| 198 | |
| 199 | loop { |
| 200 | let r = libc::fork(); |
| 201 | if r == -1 as libc::pid_t && errno() as libc::c_int == libc::EBADF { |
| 202 | if delay < get_clock_resolution() { |
| 203 | // We cannot sleep this short (it would be longer). |
| 204 | // Yield instead. |
| 205 | thread::yield_now(); |
| 206 | } else if delay < MAX_FORKSPAWN_SLEEP { |
| 207 | thread::sleep(delay); |
| 208 | } else { |
| 209 | return Err(io::const_error!( |
| 210 | ErrorKind::WouldBlock, |
| 211 | "forking returned EBADF too often" , |
| 212 | )); |
| 213 | } |
| 214 | delay *= 2; |
| 215 | continue; |
| 216 | } else { |
| 217 | return cvt(r); |
| 218 | } |
| 219 | } |
| 220 | } |
| 221 | |
| 222 | pub fn exec(&mut self, default: Stdio) -> io::Error { |
| 223 | let envp = self.capture_env(); |
| 224 | |
| 225 | if self.saw_nul() { |
| 226 | return io::const_error!(ErrorKind::InvalidInput, "nul byte found in provided data" ); |
| 227 | } |
| 228 | |
| 229 | match self.setup_io(default, true) { |
| 230 | Ok((_, theirs)) => { |
| 231 | unsafe { |
| 232 | // Similar to when forking, we want to ensure that access to |
| 233 | // the environment is synchronized, so make sure to grab the |
| 234 | // environment lock before we try to exec. |
| 235 | let _lock = sys::env::env_read_lock(); |
| 236 | |
| 237 | let Err(e) = self.do_exec(theirs, envp.as_ref()); |
| 238 | e |
| 239 | } |
| 240 | } |
| 241 | Err(e) => e, |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | // And at this point we've reached a special time in the life of the |
| 246 | // child. The child must now be considered hamstrung and unable to |
| 247 | // do anything other than syscalls really. Consider the following |
| 248 | // scenario: |
| 249 | // |
| 250 | // 1. Thread A of process 1 grabs the malloc() mutex |
| 251 | // 2. Thread B of process 1 forks(), creating thread C |
| 252 | // 3. Thread C of process 2 then attempts to malloc() |
| 253 | // 4. The memory of process 2 is the same as the memory of |
| 254 | // process 1, so the mutex is locked. |
| 255 | // |
| 256 | // This situation looks a lot like deadlock, right? It turns out |
| 257 | // that this is what pthread_atfork() takes care of, which is |
| 258 | // presumably implemented across platforms. The first thing that |
| 259 | // threads to *before* forking is to do things like grab the malloc |
| 260 | // mutex, and then after the fork they unlock it. |
| 261 | // |
| 262 | // Despite this information, libnative's spawn has been witnessed to |
| 263 | // deadlock on both macOS and FreeBSD. I'm not entirely sure why, but |
| 264 | // all collected backtraces point at malloc/free traffic in the |
| 265 | // child spawned process. |
| 266 | // |
| 267 | // For this reason, the block of code below should contain 0 |
| 268 | // invocations of either malloc of free (or their related friends). |
| 269 | // |
| 270 | // As an example of not having malloc/free traffic, we don't close |
| 271 | // this file descriptor by dropping the FileDesc (which contains an |
| 272 | // allocation). Instead we just close it manually. This will never |
| 273 | // have the drop glue anyway because this code never returns (the |
| 274 | // child will either exec() or invoke libc::exit) |
| 275 | #[cfg (not(any(target_os = "tvos" , target_os = "watchos" )))] |
| 276 | unsafe fn do_exec( |
| 277 | &mut self, |
| 278 | stdio: ChildPipes, |
| 279 | maybe_envp: Option<&CStringArray>, |
| 280 | ) -> Result<!, io::Error> { |
| 281 | use crate::sys::{self, cvt_r}; |
| 282 | |
| 283 | if let Some(fd) = stdio.stdin.fd() { |
| 284 | cvt_r(|| libc::dup2(fd, libc::STDIN_FILENO))?; |
| 285 | } |
| 286 | if let Some(fd) = stdio.stdout.fd() { |
| 287 | cvt_r(|| libc::dup2(fd, libc::STDOUT_FILENO))?; |
| 288 | } |
| 289 | if let Some(fd) = stdio.stderr.fd() { |
| 290 | cvt_r(|| libc::dup2(fd, libc::STDERR_FILENO))?; |
| 291 | } |
| 292 | |
| 293 | #[cfg (not(target_os = "l4re" ))] |
| 294 | { |
| 295 | if let Some(_g) = self.get_groups() { |
| 296 | //FIXME: Redox kernel does not support setgroups yet |
| 297 | #[cfg (not(target_os = "redox" ))] |
| 298 | cvt(libc::setgroups(_g.len().try_into().unwrap(), _g.as_ptr()))?; |
| 299 | } |
| 300 | if let Some(u) = self.get_gid() { |
| 301 | cvt(libc::setgid(u as gid_t))?; |
| 302 | } |
| 303 | if let Some(u) = self.get_uid() { |
| 304 | // When dropping privileges from root, the `setgroups` call |
| 305 | // will remove any extraneous groups. We only drop groups |
| 306 | // if we have CAP_SETGID and we weren't given an explicit |
| 307 | // set of groups. If we don't call this, then even though our |
| 308 | // uid has dropped, we may still have groups that enable us to |
| 309 | // do super-user things. |
| 310 | //FIXME: Redox kernel does not support setgroups yet |
| 311 | #[cfg (not(target_os = "redox" ))] |
| 312 | if self.get_groups().is_none() { |
| 313 | let res = cvt(libc::setgroups(0, crate::ptr::null())); |
| 314 | if let Err(e) = res { |
| 315 | // Here we ignore the case of not having CAP_SETGID. |
| 316 | // An alternative would be to require CAP_SETGID (in |
| 317 | // addition to CAP_SETUID) for setting the UID. |
| 318 | if e.raw_os_error() != Some(libc::EPERM) { |
| 319 | return Err(e.into()); |
| 320 | } |
| 321 | } |
| 322 | } |
| 323 | cvt(libc::setuid(u as uid_t))?; |
| 324 | } |
| 325 | } |
| 326 | if let Some(chroot) = self.get_chroot() { |
| 327 | #[cfg (not(target_os = "fuchsia" ))] |
| 328 | cvt(libc::chroot(chroot.as_ptr()))?; |
| 329 | #[cfg (target_os = "fuchsia" )] |
| 330 | return Err(io::const_error!( |
| 331 | io::ErrorKind::Unsupported, |
| 332 | "chroot not supported by fuchsia" |
| 333 | )); |
| 334 | } |
| 335 | if let Some(cwd) = self.get_cwd() { |
| 336 | cvt(libc::chdir(cwd.as_ptr()))?; |
| 337 | } |
| 338 | |
| 339 | if let Some(pgroup) = self.get_pgroup() { |
| 340 | cvt(libc::setpgid(0, pgroup))?; |
| 341 | } |
| 342 | |
| 343 | // emscripten has no signal support. |
| 344 | #[cfg (not(target_os = "emscripten" ))] |
| 345 | { |
| 346 | // Inherit the signal mask from the parent rather than resetting it (i.e. do not call |
| 347 | // pthread_sigmask). |
| 348 | |
| 349 | // If -Zon-broken-pipe is used, don't reset SIGPIPE to SIG_DFL. |
| 350 | // If -Zon-broken-pipe is not used, reset SIGPIPE to SIG_DFL for backward compatibility. |
| 351 | // |
| 352 | // -Zon-broken-pipe is an opportunity to change the default here. |
| 353 | if !crate::sys::pal::on_broken_pipe_flag_used() { |
| 354 | #[cfg (target_os = "android" )] // see issue #88585 |
| 355 | { |
| 356 | let mut action: libc::sigaction = mem::zeroed(); |
| 357 | action.sa_sigaction = libc::SIG_DFL; |
| 358 | cvt(libc::sigaction(libc::SIGPIPE, &action, crate::ptr::null_mut()))?; |
| 359 | } |
| 360 | #[cfg (not(target_os = "android" ))] |
| 361 | { |
| 362 | let ret = sys::signal(libc::SIGPIPE, libc::SIG_DFL); |
| 363 | if ret == libc::SIG_ERR { |
| 364 | return Err(io::Error::last_os_error()); |
| 365 | } |
| 366 | } |
| 367 | #[cfg (target_os = "hurd" )] |
| 368 | { |
| 369 | let ret = sys::signal(libc::SIGLOST, libc::SIG_DFL); |
| 370 | if ret == libc::SIG_ERR { |
| 371 | return Err(io::Error::last_os_error()); |
| 372 | } |
| 373 | } |
| 374 | } |
| 375 | } |
| 376 | |
| 377 | for callback in self.get_closures().iter_mut() { |
| 378 | callback()?; |
| 379 | } |
| 380 | |
| 381 | // Although we're performing an exec here we may also return with an |
| 382 | // error from this function (without actually exec'ing) in which case we |
| 383 | // want to be sure to restore the global environment back to what it |
| 384 | // once was, ensuring that our temporary override, when free'd, doesn't |
| 385 | // corrupt our process's environment. |
| 386 | let mut _reset = None; |
| 387 | if let Some(envp) = maybe_envp { |
| 388 | struct Reset(*const *const libc::c_char); |
| 389 | |
| 390 | impl Drop for Reset { |
| 391 | fn drop(&mut self) { |
| 392 | unsafe { |
| 393 | *sys::env::environ() = self.0; |
| 394 | } |
| 395 | } |
| 396 | } |
| 397 | |
| 398 | _reset = Some(Reset(*sys::env::environ())); |
| 399 | *sys::env::environ() = envp.as_ptr(); |
| 400 | } |
| 401 | |
| 402 | libc::execvp(self.get_program_cstr().as_ptr(), self.get_argv().as_ptr()); |
| 403 | Err(io::Error::last_os_error()) |
| 404 | } |
| 405 | |
| 406 | #[cfg (any(target_os = "tvos" , target_os = "watchos" ))] |
| 407 | unsafe fn do_exec( |
| 408 | &mut self, |
| 409 | _stdio: ChildPipes, |
| 410 | _maybe_envp: Option<&CStringArray>, |
| 411 | ) -> Result<!, io::Error> { |
| 412 | return Err(Self::ERR_APPLE_TV_WATCH_NO_FORK_EXEC); |
| 413 | } |
| 414 | |
| 415 | #[cfg (not(any( |
| 416 | target_os = "freebsd" , |
| 417 | target_os = "illumos" , |
| 418 | all(target_os = "linux" , target_env = "gnu" ), |
| 419 | all(target_os = "linux" , target_env = "musl" ), |
| 420 | target_os = "nto" , |
| 421 | target_vendor = "apple" , |
| 422 | target_os = "cygwin" , |
| 423 | )))] |
| 424 | fn posix_spawn( |
| 425 | &mut self, |
| 426 | _: &ChildPipes, |
| 427 | _: Option<&CStringArray>, |
| 428 | ) -> io::Result<Option<Process>> { |
| 429 | Ok(None) |
| 430 | } |
| 431 | |
| 432 | // Only support platforms for which posix_spawn() can return ENOENT |
| 433 | // directly. |
| 434 | #[cfg (any( |
| 435 | target_os = "freebsd" , |
| 436 | target_os = "illumos" , |
| 437 | all(target_os = "linux" , target_env = "gnu" ), |
| 438 | all(target_os = "linux" , target_env = "musl" ), |
| 439 | target_os = "nto" , |
| 440 | target_vendor = "apple" , |
| 441 | target_os = "cygwin" , |
| 442 | ))] |
| 443 | fn posix_spawn( |
| 444 | &mut self, |
| 445 | stdio: &ChildPipes, |
| 446 | envp: Option<&CStringArray>, |
| 447 | ) -> io::Result<Option<Process>> { |
| 448 | #[cfg (target_os = "linux" )] |
| 449 | use core::sync::atomic::{Atomic, AtomicU8, Ordering}; |
| 450 | |
| 451 | use crate::mem::MaybeUninit; |
| 452 | use crate::sys::{self, cvt_nz, on_broken_pipe_flag_used}; |
| 453 | |
| 454 | if self.get_gid().is_some() |
| 455 | || self.get_uid().is_some() |
| 456 | || (self.env_saw_path() && !self.program_is_path()) |
| 457 | || !self.get_closures().is_empty() |
| 458 | || self.get_groups().is_some() |
| 459 | || self.get_chroot().is_some() |
| 460 | { |
| 461 | return Ok(None); |
| 462 | } |
| 463 | |
| 464 | cfg_if::cfg_if! { |
| 465 | if #[cfg(target_os = "linux" )] { |
| 466 | use crate::sys::weak::weak; |
| 467 | |
| 468 | weak!( |
| 469 | fn pidfd_spawnp( |
| 470 | pidfd: *mut libc::c_int, |
| 471 | path: *const libc::c_char, |
| 472 | file_actions: *const libc::posix_spawn_file_actions_t, |
| 473 | attrp: *const libc::posix_spawnattr_t, |
| 474 | argv: *const *mut libc::c_char, |
| 475 | envp: *const *mut libc::c_char, |
| 476 | ) -> libc::c_int; |
| 477 | ); |
| 478 | |
| 479 | weak!( |
| 480 | fn pidfd_getpid(pidfd: libc::c_int) -> libc::c_int; |
| 481 | ); |
| 482 | |
| 483 | static PIDFD_SUPPORTED: Atomic<u8> = AtomicU8::new(0); |
| 484 | const UNKNOWN: u8 = 0; |
| 485 | const SPAWN: u8 = 1; |
| 486 | // Obtaining a pidfd via the fork+exec path might work |
| 487 | const FORK_EXEC: u8 = 2; |
| 488 | // Neither pidfd_spawn nor fork/exec will get us a pidfd. |
| 489 | // Instead we'll just posix_spawn if the other preconditions are met. |
| 490 | const NO: u8 = 3; |
| 491 | |
| 492 | if self.get_create_pidfd() { |
| 493 | let mut support = PIDFD_SUPPORTED.load(Ordering::Relaxed); |
| 494 | if support == FORK_EXEC { |
| 495 | return Ok(None); |
| 496 | } |
| 497 | if support == UNKNOWN { |
| 498 | support = NO; |
| 499 | let our_pid = crate::process::id(); |
| 500 | let pidfd = cvt(unsafe { libc::syscall(libc::SYS_pidfd_open, our_pid, 0) } as c_int); |
| 501 | match pidfd { |
| 502 | Ok(pidfd) => { |
| 503 | support = FORK_EXEC; |
| 504 | if let Some(Ok(pid)) = pidfd_getpid.get().map(|f| cvt(unsafe { f(pidfd) } as i32)) { |
| 505 | if pidfd_spawnp.get().is_some() && pid as u32 == our_pid { |
| 506 | support = SPAWN |
| 507 | } |
| 508 | } |
| 509 | unsafe { libc::close(pidfd) }; |
| 510 | } |
| 511 | Err(e) if e.raw_os_error() == Some(libc::EMFILE) => { |
| 512 | // We're temporarily(?) out of file descriptors. In this case obtaining a pidfd would also fail |
| 513 | // Don't update the support flag so we can probe again later. |
| 514 | return Err(e) |
| 515 | } |
| 516 | _ => {} |
| 517 | } |
| 518 | PIDFD_SUPPORTED.store(support, Ordering::Relaxed); |
| 519 | if support == FORK_EXEC { |
| 520 | return Ok(None); |
| 521 | } |
| 522 | } |
| 523 | core::assert_matches::debug_assert_matches!(support, SPAWN | NO); |
| 524 | } |
| 525 | } else { |
| 526 | if self.get_create_pidfd() { |
| 527 | unreachable!("only implemented on linux" ) |
| 528 | } |
| 529 | } |
| 530 | } |
| 531 | |
| 532 | // Only glibc 2.24+ posix_spawn() supports returning ENOENT directly. |
| 533 | #[cfg (all(target_os = "linux" , target_env = "gnu" ))] |
| 534 | { |
| 535 | if let Some(version) = sys::os::glibc_version() { |
| 536 | if version < (2, 24) { |
| 537 | return Ok(None); |
| 538 | } |
| 539 | } else { |
| 540 | return Ok(None); |
| 541 | } |
| 542 | } |
| 543 | |
| 544 | // On QNX Neutrino, posix_spawnp can fail with EBADF in case "another thread might have opened |
| 545 | // or closed a file descriptor while the posix_spawn() was occurring". |
| 546 | // Documentation says "... or try calling posix_spawn() again". This is what we do here. |
| 547 | // See also http://www.qnx.com/developers/docs/7.1/#com.qnx.doc.neutrino.lib_ref/topic/p/posix_spawn.html |
| 548 | #[cfg (target_os = "nto" )] |
| 549 | unsafe fn retrying_libc_posix_spawnp( |
| 550 | pid: *mut pid_t, |
| 551 | file: *const c_char, |
| 552 | file_actions: *const posix_spawn_file_actions_t, |
| 553 | attrp: *const posix_spawnattr_t, |
| 554 | argv: *const *mut c_char, |
| 555 | envp: *const *mut c_char, |
| 556 | ) -> io::Result<i32> { |
| 557 | let mut delay = MIN_FORKSPAWN_SLEEP; |
| 558 | loop { |
| 559 | match libc::posix_spawnp(pid, file, file_actions, attrp, argv, envp) { |
| 560 | libc::EBADF => { |
| 561 | if delay < get_clock_resolution() { |
| 562 | // We cannot sleep this short (it would be longer). |
| 563 | // Yield instead. |
| 564 | thread::yield_now(); |
| 565 | } else if delay < MAX_FORKSPAWN_SLEEP { |
| 566 | thread::sleep(delay); |
| 567 | } else { |
| 568 | return Err(io::const_error!( |
| 569 | ErrorKind::WouldBlock, |
| 570 | "posix_spawnp returned EBADF too often" , |
| 571 | )); |
| 572 | } |
| 573 | delay *= 2; |
| 574 | continue; |
| 575 | } |
| 576 | r => { |
| 577 | return Ok(r); |
| 578 | } |
| 579 | } |
| 580 | } |
| 581 | } |
| 582 | |
| 583 | type PosixSpawnAddChdirFn = unsafe extern "C" fn( |
| 584 | *mut libc::posix_spawn_file_actions_t, |
| 585 | *const libc::c_char, |
| 586 | ) -> libc::c_int; |
| 587 | |
| 588 | /// Get the function pointer for adding a chdir action to a |
| 589 | /// `posix_spawn_file_actions_t`, if available, assuming a dynamic libc. |
| 590 | /// |
| 591 | /// Some platforms can set a new working directory for a spawned process in the |
| 592 | /// `posix_spawn` path. This function looks up the function pointer for adding |
| 593 | /// such an action to a `posix_spawn_file_actions_t` struct. |
| 594 | #[cfg (not(any(all(target_os = "linux" , target_env = "musl" ), target_os = "cygwin" )))] |
| 595 | fn get_posix_spawn_addchdir() -> Option<PosixSpawnAddChdirFn> { |
| 596 | use crate::sys::weak::weak; |
| 597 | |
| 598 | // POSIX.1-2024 standardizes this function: |
| 599 | // https://pubs.opengroup.org/onlinepubs/9799919799/functions/posix_spawn_file_actions_addchdir.html. |
| 600 | // The _np version is more widely available, though, so try that first. |
| 601 | |
| 602 | weak!( |
| 603 | fn posix_spawn_file_actions_addchdir_np( |
| 604 | file_actions: *mut libc::posix_spawn_file_actions_t, |
| 605 | path: *const libc::c_char, |
| 606 | ) -> libc::c_int; |
| 607 | ); |
| 608 | |
| 609 | weak!( |
| 610 | fn posix_spawn_file_actions_addchdir( |
| 611 | file_actions: *mut libc::posix_spawn_file_actions_t, |
| 612 | path: *const libc::c_char, |
| 613 | ) -> libc::c_int; |
| 614 | ); |
| 615 | |
| 616 | posix_spawn_file_actions_addchdir_np |
| 617 | .get() |
| 618 | .or_else(|| posix_spawn_file_actions_addchdir.get()) |
| 619 | } |
| 620 | |
| 621 | /// Get the function pointer for adding a chdir action to a |
| 622 | /// `posix_spawn_file_actions_t`, if available, on platforms where the function |
| 623 | /// is known to exist. |
| 624 | /// |
| 625 | /// Weak symbol lookup doesn't work with statically linked libcs, so in cases |
| 626 | /// where static linking is possible we need to either check for the presence |
| 627 | /// of the symbol at compile time or know about it upfront. |
| 628 | /// |
| 629 | /// Cygwin doesn't support weak symbol, so just link it. |
| 630 | #[cfg (any(all(target_os = "linux" , target_env = "musl" ), target_os = "cygwin" ))] |
| 631 | fn get_posix_spawn_addchdir() -> Option<PosixSpawnAddChdirFn> { |
| 632 | // Our minimum required musl supports this function, so we can just use it. |
| 633 | Some(libc::posix_spawn_file_actions_addchdir_np) |
| 634 | } |
| 635 | |
| 636 | let addchdir = match self.get_cwd() { |
| 637 | Some(cwd) => { |
| 638 | if cfg!(target_vendor = "apple" ) { |
| 639 | // There is a bug in macOS where a relative executable |
| 640 | // path like "../myprogram" will cause `posix_spawn` to |
| 641 | // successfully launch the program, but erroneously return |
| 642 | // ENOENT when used with posix_spawn_file_actions_addchdir_np |
| 643 | // which was introduced in macOS 10.15. |
| 644 | if self.get_program_kind() == ProgramKind::Relative { |
| 645 | return Ok(None); |
| 646 | } |
| 647 | } |
| 648 | // Check for the availability of the posix_spawn addchdir |
| 649 | // function now. If it isn't available, bail and use the |
| 650 | // fork/exec path. |
| 651 | match get_posix_spawn_addchdir() { |
| 652 | Some(f) => Some((f, cwd)), |
| 653 | None => return Ok(None), |
| 654 | } |
| 655 | } |
| 656 | None => None, |
| 657 | }; |
| 658 | |
| 659 | let pgroup = self.get_pgroup(); |
| 660 | |
| 661 | struct PosixSpawnFileActions<'a>(&'a mut MaybeUninit<libc::posix_spawn_file_actions_t>); |
| 662 | |
| 663 | impl Drop for PosixSpawnFileActions<'_> { |
| 664 | fn drop(&mut self) { |
| 665 | unsafe { |
| 666 | libc::posix_spawn_file_actions_destroy(self.0.as_mut_ptr()); |
| 667 | } |
| 668 | } |
| 669 | } |
| 670 | |
| 671 | struct PosixSpawnattr<'a>(&'a mut MaybeUninit<libc::posix_spawnattr_t>); |
| 672 | |
| 673 | impl Drop for PosixSpawnattr<'_> { |
| 674 | fn drop(&mut self) { |
| 675 | unsafe { |
| 676 | libc::posix_spawnattr_destroy(self.0.as_mut_ptr()); |
| 677 | } |
| 678 | } |
| 679 | } |
| 680 | |
| 681 | unsafe { |
| 682 | let mut attrs = MaybeUninit::uninit(); |
| 683 | cvt_nz(libc::posix_spawnattr_init(attrs.as_mut_ptr()))?; |
| 684 | let attrs = PosixSpawnattr(&mut attrs); |
| 685 | |
| 686 | let mut flags = 0; |
| 687 | |
| 688 | let mut file_actions = MaybeUninit::uninit(); |
| 689 | cvt_nz(libc::posix_spawn_file_actions_init(file_actions.as_mut_ptr()))?; |
| 690 | let file_actions = PosixSpawnFileActions(&mut file_actions); |
| 691 | |
| 692 | if let Some(fd) = stdio.stdin.fd() { |
| 693 | cvt_nz(libc::posix_spawn_file_actions_adddup2( |
| 694 | file_actions.0.as_mut_ptr(), |
| 695 | fd, |
| 696 | libc::STDIN_FILENO, |
| 697 | ))?; |
| 698 | } |
| 699 | if let Some(fd) = stdio.stdout.fd() { |
| 700 | cvt_nz(libc::posix_spawn_file_actions_adddup2( |
| 701 | file_actions.0.as_mut_ptr(), |
| 702 | fd, |
| 703 | libc::STDOUT_FILENO, |
| 704 | ))?; |
| 705 | } |
| 706 | if let Some(fd) = stdio.stderr.fd() { |
| 707 | cvt_nz(libc::posix_spawn_file_actions_adddup2( |
| 708 | file_actions.0.as_mut_ptr(), |
| 709 | fd, |
| 710 | libc::STDERR_FILENO, |
| 711 | ))?; |
| 712 | } |
| 713 | if let Some((f, cwd)) = addchdir { |
| 714 | cvt_nz(f(file_actions.0.as_mut_ptr(), cwd.as_ptr()))?; |
| 715 | } |
| 716 | |
| 717 | if let Some(pgroup) = pgroup { |
| 718 | flags |= libc::POSIX_SPAWN_SETPGROUP; |
| 719 | cvt_nz(libc::posix_spawnattr_setpgroup(attrs.0.as_mut_ptr(), pgroup))?; |
| 720 | } |
| 721 | |
| 722 | // Inherit the signal mask from this process rather than resetting it (i.e. do not call |
| 723 | // posix_spawnattr_setsigmask). |
| 724 | |
| 725 | // If -Zon-broken-pipe is used, don't reset SIGPIPE to SIG_DFL. |
| 726 | // If -Zon-broken-pipe is not used, reset SIGPIPE to SIG_DFL for backward compatibility. |
| 727 | // |
| 728 | // -Zon-broken-pipe is an opportunity to change the default here. |
| 729 | if !on_broken_pipe_flag_used() { |
| 730 | let mut default_set = MaybeUninit::<libc::sigset_t>::uninit(); |
| 731 | cvt(sigemptyset(default_set.as_mut_ptr()))?; |
| 732 | cvt(sigaddset(default_set.as_mut_ptr(), libc::SIGPIPE))?; |
| 733 | #[cfg (target_os = "hurd" )] |
| 734 | { |
| 735 | cvt(sigaddset(default_set.as_mut_ptr(), libc::SIGLOST))?; |
| 736 | } |
| 737 | cvt_nz(libc::posix_spawnattr_setsigdefault( |
| 738 | attrs.0.as_mut_ptr(), |
| 739 | default_set.as_ptr(), |
| 740 | ))?; |
| 741 | flags |= libc::POSIX_SPAWN_SETSIGDEF; |
| 742 | } |
| 743 | |
| 744 | cvt_nz(libc::posix_spawnattr_setflags(attrs.0.as_mut_ptr(), flags as _))?; |
| 745 | |
| 746 | // Make sure we synchronize access to the global `environ` resource |
| 747 | let _env_lock = sys::env::env_read_lock(); |
| 748 | let envp = envp.map(|c| c.as_ptr()).unwrap_or_else(|| *sys::env::environ() as *const _); |
| 749 | |
| 750 | #[cfg (not(target_os = "nto" ))] |
| 751 | let spawn_fn = libc::posix_spawnp; |
| 752 | #[cfg (target_os = "nto" )] |
| 753 | let spawn_fn = retrying_libc_posix_spawnp; |
| 754 | |
| 755 | #[cfg (target_os = "linux" )] |
| 756 | if self.get_create_pidfd() && PIDFD_SUPPORTED.load(Ordering::Relaxed) == SPAWN { |
| 757 | let mut pidfd: libc::c_int = -1; |
| 758 | let spawn_res = pidfd_spawnp.get().unwrap()( |
| 759 | &mut pidfd, |
| 760 | self.get_program_cstr().as_ptr(), |
| 761 | file_actions.0.as_ptr(), |
| 762 | attrs.0.as_ptr(), |
| 763 | self.get_argv().as_ptr() as *const _, |
| 764 | envp as *const _, |
| 765 | ); |
| 766 | |
| 767 | let spawn_res = cvt_nz(spawn_res); |
| 768 | if let Err(ref e) = spawn_res |
| 769 | && e.raw_os_error() == Some(libc::ENOSYS) |
| 770 | { |
| 771 | PIDFD_SUPPORTED.store(FORK_EXEC, Ordering::Relaxed); |
| 772 | return Ok(None); |
| 773 | } |
| 774 | spawn_res?; |
| 775 | |
| 776 | let pid = match cvt(pidfd_getpid.get().unwrap()(pidfd)) { |
| 777 | Ok(pid) => pid, |
| 778 | Err(e) => { |
| 779 | // The child has been spawned and we are holding its pidfd. |
| 780 | // But we cannot obtain its pid even though pidfd_getpid support was verified earlier. |
| 781 | // This might happen if libc can't open procfs because the file descriptor limit has been reached. |
| 782 | libc::close(pidfd); |
| 783 | return Err(Error::new( |
| 784 | e.kind(), |
| 785 | "pidfd_spawnp succeeded but the child's PID could not be obtained" , |
| 786 | )); |
| 787 | } |
| 788 | }; |
| 789 | |
| 790 | return Ok(Some(Process::new(pid, pidfd))); |
| 791 | } |
| 792 | |
| 793 | // Safety: -1 indicates we don't have a pidfd. |
| 794 | let mut p = Process::new(0, -1); |
| 795 | |
| 796 | let spawn_res = spawn_fn( |
| 797 | &mut p.pid, |
| 798 | self.get_program_cstr().as_ptr(), |
| 799 | file_actions.0.as_ptr(), |
| 800 | attrs.0.as_ptr(), |
| 801 | self.get_argv().as_ptr() as *const _, |
| 802 | envp as *const _, |
| 803 | ); |
| 804 | |
| 805 | #[cfg (target_os = "nto" )] |
| 806 | let spawn_res = spawn_res?; |
| 807 | |
| 808 | cvt_nz(spawn_res)?; |
| 809 | Ok(Some(p)) |
| 810 | } |
| 811 | } |
| 812 | |
| 813 | #[cfg (target_os = "linux" )] |
| 814 | fn send_pidfd(&self, sock: &crate::sys::net::Socket) { |
| 815 | use libc::{CMSG_DATA, CMSG_FIRSTHDR, CMSG_LEN, CMSG_SPACE, SCM_RIGHTS, SOL_SOCKET}; |
| 816 | |
| 817 | use crate::io::IoSlice; |
| 818 | use crate::os::fd::RawFd; |
| 819 | use crate::sys::cvt_r; |
| 820 | |
| 821 | unsafe { |
| 822 | let child_pid = libc::getpid(); |
| 823 | // pidfd_open sets CLOEXEC by default |
| 824 | let pidfd = libc::syscall(libc::SYS_pidfd_open, child_pid, 0); |
| 825 | |
| 826 | let fds: [c_int; 1] = [pidfd as RawFd]; |
| 827 | |
| 828 | const SCM_MSG_LEN: usize = size_of::<[c_int; 1]>(); |
| 829 | |
| 830 | #[repr (C)] |
| 831 | union Cmsg { |
| 832 | buf: [u8; unsafe { CMSG_SPACE(SCM_MSG_LEN as u32) as usize }], |
| 833 | _align: libc::cmsghdr, |
| 834 | } |
| 835 | |
| 836 | let mut cmsg: Cmsg = mem::zeroed(); |
| 837 | |
| 838 | // 0-length message to send through the socket so we can pass along the fd |
| 839 | let mut iov = [IoSlice::new(b"" )]; |
| 840 | let mut msg: libc::msghdr = mem::zeroed(); |
| 841 | |
| 842 | msg.msg_iov = (&raw mut iov) as *mut _; |
| 843 | msg.msg_iovlen = 1; |
| 844 | |
| 845 | // only attach cmsg if we successfully acquired the pidfd |
| 846 | if pidfd >= 0 { |
| 847 | msg.msg_controllen = size_of_val(&cmsg.buf) as _; |
| 848 | msg.msg_control = (&raw mut cmsg.buf) as *mut _; |
| 849 | |
| 850 | let hdr = CMSG_FIRSTHDR((&raw mut msg) as *mut _); |
| 851 | (*hdr).cmsg_level = SOL_SOCKET; |
| 852 | (*hdr).cmsg_type = SCM_RIGHTS; |
| 853 | (*hdr).cmsg_len = CMSG_LEN(SCM_MSG_LEN as _) as _; |
| 854 | let data = CMSG_DATA(hdr); |
| 855 | crate::ptr::copy_nonoverlapping( |
| 856 | fds.as_ptr().cast::<u8>(), |
| 857 | data as *mut _, |
| 858 | SCM_MSG_LEN, |
| 859 | ); |
| 860 | } |
| 861 | |
| 862 | // we send the 0-length message even if we failed to acquire the pidfd |
| 863 | // so we get a consistent SEQPACKET order |
| 864 | match cvt_r(|| libc::sendmsg(sock.as_raw(), &msg, 0)) { |
| 865 | Ok(0) => {} |
| 866 | other => rtabort!("failed to communicate with parent process. {:?}" , other), |
| 867 | } |
| 868 | } |
| 869 | } |
| 870 | |
| 871 | #[cfg (target_os = "linux" )] |
| 872 | fn recv_pidfd(&self, sock: &crate::sys::net::Socket) -> pid_t { |
| 873 | use libc::{CMSG_DATA, CMSG_FIRSTHDR, CMSG_LEN, CMSG_SPACE, SCM_RIGHTS, SOL_SOCKET}; |
| 874 | |
| 875 | use crate::io::IoSliceMut; |
| 876 | use crate::sys::cvt_r; |
| 877 | |
| 878 | unsafe { |
| 879 | const SCM_MSG_LEN: usize = size_of::<[c_int; 1]>(); |
| 880 | |
| 881 | #[repr (C)] |
| 882 | union Cmsg { |
| 883 | _buf: [u8; unsafe { CMSG_SPACE(SCM_MSG_LEN as u32) as usize }], |
| 884 | _align: libc::cmsghdr, |
| 885 | } |
| 886 | let mut cmsg: Cmsg = mem::zeroed(); |
| 887 | // 0-length read to get the fd |
| 888 | let mut iov = [IoSliceMut::new(&mut [])]; |
| 889 | |
| 890 | let mut msg: libc::msghdr = mem::zeroed(); |
| 891 | |
| 892 | msg.msg_iov = (&raw mut iov) as *mut _; |
| 893 | msg.msg_iovlen = 1; |
| 894 | msg.msg_controllen = size_of::<Cmsg>() as _; |
| 895 | msg.msg_control = (&raw mut cmsg) as *mut _; |
| 896 | |
| 897 | match cvt_r(|| libc::recvmsg(sock.as_raw(), &mut msg, libc::MSG_CMSG_CLOEXEC)) { |
| 898 | Err(_) => return -1, |
| 899 | Ok(_) => {} |
| 900 | } |
| 901 | |
| 902 | let hdr = CMSG_FIRSTHDR((&raw mut msg) as *mut _); |
| 903 | if hdr.is_null() |
| 904 | || (*hdr).cmsg_level != SOL_SOCKET |
| 905 | || (*hdr).cmsg_type != SCM_RIGHTS |
| 906 | || (*hdr).cmsg_len != CMSG_LEN(SCM_MSG_LEN as _) as _ |
| 907 | { |
| 908 | return -1; |
| 909 | } |
| 910 | let data = CMSG_DATA(hdr); |
| 911 | |
| 912 | let mut fds = [-1 as c_int]; |
| 913 | |
| 914 | crate::ptr::copy_nonoverlapping( |
| 915 | data as *const _, |
| 916 | fds.as_mut_ptr().cast::<u8>(), |
| 917 | SCM_MSG_LEN, |
| 918 | ); |
| 919 | |
| 920 | fds[0] |
| 921 | } |
| 922 | } |
| 923 | } |
| 924 | |
| 925 | //////////////////////////////////////////////////////////////////////////////// |
| 926 | // Processes |
| 927 | //////////////////////////////////////////////////////////////////////////////// |
| 928 | |
| 929 | /// The unique ID of the process (this should never be negative). |
| 930 | pub struct Process { |
| 931 | pid: pid_t, |
| 932 | status: Option<ExitStatus>, |
| 933 | // On Linux, stores the pidfd created for this child. |
| 934 | // This is None if the user did not request pidfd creation, |
| 935 | // or if the pidfd could not be created for some reason |
| 936 | // (e.g. the `pidfd_open` syscall was not available). |
| 937 | #[cfg (target_os = "linux" )] |
| 938 | pidfd: Option<PidFd>, |
| 939 | } |
| 940 | |
| 941 | impl Process { |
| 942 | #[cfg (target_os = "linux" )] |
| 943 | /// # Safety |
| 944 | /// |
| 945 | /// `pidfd` must either be -1 (representing no file descriptor) or a valid, exclusively owned file |
| 946 | /// descriptor (See [I/O Safety]). |
| 947 | /// |
| 948 | /// [I/O Safety]: crate::io#io-safety |
| 949 | unsafe fn new(pid: pid_t, pidfd: pid_t) -> Self { |
| 950 | use crate::os::unix::io::FromRawFd; |
| 951 | use crate::sys_common::FromInner; |
| 952 | // Safety: If `pidfd` is nonnegative, we assume it's valid and otherwise unowned. |
| 953 | let pidfd = (pidfd >= 0).then(|| PidFd::from_inner(sys::fd::FileDesc::from_raw_fd(pidfd))); |
| 954 | Process { pid, status: None, pidfd } |
| 955 | } |
| 956 | |
| 957 | #[cfg (not(target_os = "linux" ))] |
| 958 | unsafe fn new(pid: pid_t, _pidfd: pid_t) -> Self { |
| 959 | Process { pid, status: None } |
| 960 | } |
| 961 | |
| 962 | pub fn id(&self) -> u32 { |
| 963 | self.pid as u32 |
| 964 | } |
| 965 | |
| 966 | pub fn kill(&mut self) -> io::Result<()> { |
| 967 | // If we've already waited on this process then the pid can be recycled |
| 968 | // and used for another process, and we probably shouldn't be killing |
| 969 | // random processes, so return Ok because the process has exited already. |
| 970 | if self.status.is_some() { |
| 971 | return Ok(()); |
| 972 | } |
| 973 | #[cfg (target_os = "linux" )] |
| 974 | if let Some(pid_fd) = self.pidfd.as_ref() { |
| 975 | // pidfd_send_signal predates pidfd_open. so if we were able to get an fd then sending signals will work too |
| 976 | return pid_fd.kill(); |
| 977 | } |
| 978 | cvt(unsafe { libc::kill(self.pid, libc::SIGKILL) }).map(drop) |
| 979 | } |
| 980 | |
| 981 | pub fn wait(&mut self) -> io::Result<ExitStatus> { |
| 982 | use crate::sys::cvt_r; |
| 983 | if let Some(status) = self.status { |
| 984 | return Ok(status); |
| 985 | } |
| 986 | #[cfg (target_os = "linux" )] |
| 987 | if let Some(pid_fd) = self.pidfd.as_ref() { |
| 988 | let status = pid_fd.wait()?; |
| 989 | self.status = Some(status); |
| 990 | return Ok(status); |
| 991 | } |
| 992 | let mut status = 0 as c_int; |
| 993 | cvt_r(|| unsafe { libc::waitpid(self.pid, &mut status, 0) })?; |
| 994 | self.status = Some(ExitStatus::new(status)); |
| 995 | Ok(ExitStatus::new(status)) |
| 996 | } |
| 997 | |
| 998 | pub fn try_wait(&mut self) -> io::Result<Option<ExitStatus>> { |
| 999 | if let Some(status) = self.status { |
| 1000 | return Ok(Some(status)); |
| 1001 | } |
| 1002 | #[cfg (target_os = "linux" )] |
| 1003 | if let Some(pid_fd) = self.pidfd.as_ref() { |
| 1004 | let status = pid_fd.try_wait()?; |
| 1005 | if let Some(status) = status { |
| 1006 | self.status = Some(status) |
| 1007 | } |
| 1008 | return Ok(status); |
| 1009 | } |
| 1010 | let mut status = 0 as c_int; |
| 1011 | let pid = cvt(unsafe { libc::waitpid(self.pid, &mut status, libc::WNOHANG) })?; |
| 1012 | if pid == 0 { |
| 1013 | Ok(None) |
| 1014 | } else { |
| 1015 | self.status = Some(ExitStatus::new(status)); |
| 1016 | Ok(Some(ExitStatus::new(status))) |
| 1017 | } |
| 1018 | } |
| 1019 | } |
| 1020 | |
| 1021 | /// Unix exit statuses |
| 1022 | // |
| 1023 | // This is not actually an "exit status" in Unix terminology. Rather, it is a "wait status". |
| 1024 | // See the discussion in comments and doc comments for `std::process::ExitStatus`. |
| 1025 | #[derive (PartialEq, Eq, Clone, Copy, Default)] |
| 1026 | pub struct ExitStatus(c_int); |
| 1027 | |
| 1028 | impl fmt::Debug for ExitStatus { |
| 1029 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 1030 | f.debug_tuple(name:"unix_wait_status" ).field(&self.0).finish() |
| 1031 | } |
| 1032 | } |
| 1033 | |
| 1034 | impl ExitStatus { |
| 1035 | pub fn new(status: c_int) -> ExitStatus { |
| 1036 | ExitStatus(status) |
| 1037 | } |
| 1038 | |
| 1039 | #[cfg (target_os = "linux" )] |
| 1040 | pub fn from_waitid_siginfo(siginfo: libc::siginfo_t) -> ExitStatus { |
| 1041 | let status = unsafe { siginfo.si_status() }; |
| 1042 | |
| 1043 | match siginfo.si_code { |
| 1044 | libc::CLD_EXITED => ExitStatus((status & 0xff) << 8), |
| 1045 | libc::CLD_KILLED => ExitStatus(status), |
| 1046 | libc::CLD_DUMPED => ExitStatus(status | 0x80), |
| 1047 | libc::CLD_CONTINUED => ExitStatus(0xffff), |
| 1048 | libc::CLD_STOPPED | libc::CLD_TRAPPED => ExitStatus(((status & 0xff) << 8) | 0x7f), |
| 1049 | _ => unreachable!("waitid() should only return the above codes" ), |
| 1050 | } |
| 1051 | } |
| 1052 | |
| 1053 | fn exited(&self) -> bool { |
| 1054 | libc::WIFEXITED(self.0) |
| 1055 | } |
| 1056 | |
| 1057 | pub fn exit_ok(&self) -> Result<(), ExitStatusError> { |
| 1058 | // This assumes that WIFEXITED(status) && WEXITSTATUS==0 corresponds to status==0. This is |
| 1059 | // true on all actual versions of Unix, is widely assumed, and is specified in SuS |
| 1060 | // https://pubs.opengroup.org/onlinepubs/9699919799/functions/wait.html. If it is not |
| 1061 | // true for a platform pretending to be Unix, the tests (our doctests, and also |
| 1062 | // unix/tests.rs) will spot it. `ExitStatusError::code` assumes this too. |
| 1063 | match NonZero::try_from(self.0) { |
| 1064 | /* was nonzero */ Ok(failure) => Err(ExitStatusError(failure)), |
| 1065 | /* was zero, couldn't convert */ Err(_) => Ok(()), |
| 1066 | } |
| 1067 | } |
| 1068 | |
| 1069 | pub fn code(&self) -> Option<i32> { |
| 1070 | self.exited().then(|| libc::WEXITSTATUS(self.0)) |
| 1071 | } |
| 1072 | |
| 1073 | pub fn signal(&self) -> Option<i32> { |
| 1074 | libc::WIFSIGNALED(self.0).then(|| libc::WTERMSIG(self.0)) |
| 1075 | } |
| 1076 | |
| 1077 | pub fn core_dumped(&self) -> bool { |
| 1078 | libc::WIFSIGNALED(self.0) && libc::WCOREDUMP(self.0) |
| 1079 | } |
| 1080 | |
| 1081 | pub fn stopped_signal(&self) -> Option<i32> { |
| 1082 | libc::WIFSTOPPED(self.0).then(|| libc::WSTOPSIG(self.0)) |
| 1083 | } |
| 1084 | |
| 1085 | pub fn continued(&self) -> bool { |
| 1086 | libc::WIFCONTINUED(self.0) |
| 1087 | } |
| 1088 | |
| 1089 | pub fn into_raw(&self) -> c_int { |
| 1090 | self.0 |
| 1091 | } |
| 1092 | } |
| 1093 | |
| 1094 | /// Converts a raw `c_int` to a type-safe `ExitStatus` by wrapping it without copying. |
| 1095 | impl From<c_int> for ExitStatus { |
| 1096 | fn from(a: c_int) -> ExitStatus { |
| 1097 | ExitStatus(a) |
| 1098 | } |
| 1099 | } |
| 1100 | |
| 1101 | /// Converts a signal number to a readable, searchable name. |
| 1102 | /// |
| 1103 | /// This string should be displayed right after the signal number. |
| 1104 | /// If a signal is unrecognized, it returns the empty string, so that |
| 1105 | /// you just get the number like "0". If it is recognized, you'll get |
| 1106 | /// something like "9 (SIGKILL)". |
| 1107 | fn signal_string(signal: i32) -> &'static str { |
| 1108 | match signal { |
| 1109 | libc::SIGHUP => " (SIGHUP)" , |
| 1110 | libc::SIGINT => " (SIGINT)" , |
| 1111 | libc::SIGQUIT => " (SIGQUIT)" , |
| 1112 | libc::SIGILL => " (SIGILL)" , |
| 1113 | libc::SIGTRAP => " (SIGTRAP)" , |
| 1114 | libc::SIGABRT => " (SIGABRT)" , |
| 1115 | #[cfg (not(target_os = "l4re" ))] |
| 1116 | libc::SIGBUS => " (SIGBUS)" , |
| 1117 | libc::SIGFPE => " (SIGFPE)" , |
| 1118 | libc::SIGKILL => " (SIGKILL)" , |
| 1119 | #[cfg (not(target_os = "l4re" ))] |
| 1120 | libc::SIGUSR1 => " (SIGUSR1)" , |
| 1121 | libc::SIGSEGV => " (SIGSEGV)" , |
| 1122 | #[cfg (not(target_os = "l4re" ))] |
| 1123 | libc::SIGUSR2 => " (SIGUSR2)" , |
| 1124 | libc::SIGPIPE => " (SIGPIPE)" , |
| 1125 | libc::SIGALRM => " (SIGALRM)" , |
| 1126 | libc::SIGTERM => " (SIGTERM)" , |
| 1127 | #[cfg (not(target_os = "l4re" ))] |
| 1128 | libc::SIGCHLD => " (SIGCHLD)" , |
| 1129 | #[cfg (not(target_os = "l4re" ))] |
| 1130 | libc::SIGCONT => " (SIGCONT)" , |
| 1131 | #[cfg (not(target_os = "l4re" ))] |
| 1132 | libc::SIGSTOP => " (SIGSTOP)" , |
| 1133 | #[cfg (not(target_os = "l4re" ))] |
| 1134 | libc::SIGTSTP => " (SIGTSTP)" , |
| 1135 | #[cfg (not(target_os = "l4re" ))] |
| 1136 | libc::SIGTTIN => " (SIGTTIN)" , |
| 1137 | #[cfg (not(target_os = "l4re" ))] |
| 1138 | libc::SIGTTOU => " (SIGTTOU)" , |
| 1139 | #[cfg (not(target_os = "l4re" ))] |
| 1140 | libc::SIGURG => " (SIGURG)" , |
| 1141 | #[cfg (not(target_os = "l4re" ))] |
| 1142 | libc::SIGXCPU => " (SIGXCPU)" , |
| 1143 | #[cfg (not(any(target_os = "l4re" , target_os = "rtems" )))] |
| 1144 | libc::SIGXFSZ => " (SIGXFSZ)" , |
| 1145 | #[cfg (not(any(target_os = "l4re" , target_os = "rtems" )))] |
| 1146 | libc::SIGVTALRM => " (SIGVTALRM)" , |
| 1147 | #[cfg (not(target_os = "l4re" ))] |
| 1148 | libc::SIGPROF => " (SIGPROF)" , |
| 1149 | #[cfg (not(any(target_os = "l4re" , target_os = "rtems" )))] |
| 1150 | libc::SIGWINCH => " (SIGWINCH)" , |
| 1151 | #[cfg (not(any(target_os = "haiku" , target_os = "l4re" )))] |
| 1152 | libc::SIGIO => " (SIGIO)" , |
| 1153 | #[cfg (target_os = "haiku" )] |
| 1154 | libc::SIGPOLL => " (SIGPOLL)" , |
| 1155 | #[cfg (not(target_os = "l4re" ))] |
| 1156 | libc::SIGSYS => " (SIGSYS)" , |
| 1157 | // For information on Linux signals, run `man 7 signal` |
| 1158 | #[cfg (all( |
| 1159 | target_os = "linux" , |
| 1160 | any( |
| 1161 | target_arch = "x86_64" , |
| 1162 | target_arch = "x86" , |
| 1163 | target_arch = "arm" , |
| 1164 | target_arch = "aarch64" |
| 1165 | ) |
| 1166 | ))] |
| 1167 | libc::SIGSTKFLT => " (SIGSTKFLT)" , |
| 1168 | #[cfg (any(target_os = "linux" , target_os = "nto" , target_os = "cygwin" ))] |
| 1169 | libc::SIGPWR => " (SIGPWR)" , |
| 1170 | #[cfg (any( |
| 1171 | target_os = "freebsd" , |
| 1172 | target_os = "netbsd" , |
| 1173 | target_os = "openbsd" , |
| 1174 | target_os = "dragonfly" , |
| 1175 | target_os = "nto" , |
| 1176 | target_vendor = "apple" , |
| 1177 | target_os = "cygwin" , |
| 1178 | ))] |
| 1179 | libc::SIGEMT => " (SIGEMT)" , |
| 1180 | #[cfg (any( |
| 1181 | target_os = "freebsd" , |
| 1182 | target_os = "netbsd" , |
| 1183 | target_os = "openbsd" , |
| 1184 | target_os = "dragonfly" , |
| 1185 | target_vendor = "apple" , |
| 1186 | ))] |
| 1187 | libc::SIGINFO => " (SIGINFO)" , |
| 1188 | #[cfg (target_os = "hurd" )] |
| 1189 | libc::SIGLOST => " (SIGLOST)" , |
| 1190 | #[cfg (target_os = "freebsd" )] |
| 1191 | libc::SIGTHR => " (SIGTHR)" , |
| 1192 | #[cfg (target_os = "freebsd" )] |
| 1193 | libc::SIGLIBRT => " (SIGLIBRT)" , |
| 1194 | _ => "" , |
| 1195 | } |
| 1196 | } |
| 1197 | |
| 1198 | impl fmt::Display for ExitStatus { |
| 1199 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 1200 | if let Some(code: i32) = self.code() { |
| 1201 | write!(f, "exit status: {code}" ) |
| 1202 | } else if let Some(signal: i32) = self.signal() { |
| 1203 | let signal_string: &'static str = signal_string(signal); |
| 1204 | if self.core_dumped() { |
| 1205 | write!(f, "signal: {signal}{signal_string} (core dumped)" ) |
| 1206 | } else { |
| 1207 | write!(f, "signal: {signal}{signal_string}" ) |
| 1208 | } |
| 1209 | } else if let Some(signal: i32) = self.stopped_signal() { |
| 1210 | let signal_string: &'static str = signal_string(signal); |
| 1211 | write!(f, "stopped (not terminated) by signal: {signal}{signal_string}" ) |
| 1212 | } else if self.continued() { |
| 1213 | write!(f, "continued (WIFCONTINUED)" ) |
| 1214 | } else { |
| 1215 | write!(f, "unrecognised wait status: {} {:#x}" , self.0, self.0) |
| 1216 | } |
| 1217 | } |
| 1218 | } |
| 1219 | |
| 1220 | #[derive (PartialEq, Eq, Clone, Copy)] |
| 1221 | pub struct ExitStatusError(NonZero<c_int>); |
| 1222 | |
| 1223 | impl Into<ExitStatus> for ExitStatusError { |
| 1224 | fn into(self) -> ExitStatus { |
| 1225 | ExitStatus(self.0.into()) |
| 1226 | } |
| 1227 | } |
| 1228 | |
| 1229 | impl fmt::Debug for ExitStatusError { |
| 1230 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 1231 | f.debug_tuple(name:"unix_wait_status" ).field(&self.0).finish() |
| 1232 | } |
| 1233 | } |
| 1234 | |
| 1235 | impl ExitStatusError { |
| 1236 | pub fn code(self) -> Option<NonZero<i32>> { |
| 1237 | ExitStatus(self.0.into()).code().map(|st: i32| st.try_into().unwrap()) |
| 1238 | } |
| 1239 | } |
| 1240 | |
| 1241 | #[cfg (target_os = "linux" )] |
| 1242 | mod linux_child_ext { |
| 1243 | use crate::io::ErrorKind; |
| 1244 | use crate::os::linux::process as os; |
| 1245 | use crate::sys::pal::linux::pidfd as imp; |
| 1246 | use crate::sys_common::FromInner; |
| 1247 | use crate::{io, mem}; |
| 1248 | |
| 1249 | #[unstable (feature = "linux_pidfd" , issue = "82971" )] |
| 1250 | impl crate::os::linux::process::ChildExt for crate::process::Child { |
| 1251 | fn pidfd(&self) -> io::Result<&os::PidFd> { |
| 1252 | self.handle |
| 1253 | .pidfd |
| 1254 | .as_ref() |
| 1255 | // SAFETY: The os type is a transparent wrapper, therefore we can transmute references |
| 1256 | .map(|fd| unsafe { mem::transmute::<&imp::PidFd, &os::PidFd>(fd) }) |
| 1257 | .ok_or_else(|| io::const_error!(ErrorKind::Uncategorized, "no pidfd was created." )) |
| 1258 | } |
| 1259 | |
| 1260 | fn into_pidfd(mut self) -> Result<os::PidFd, Self> { |
| 1261 | self.handle |
| 1262 | .pidfd |
| 1263 | .take() |
| 1264 | .map(|fd| <os::PidFd as FromInner<imp::PidFd>>::from_inner(fd)) |
| 1265 | .ok_or_else(|| self) |
| 1266 | } |
| 1267 | } |
| 1268 | } |
| 1269 | |
| 1270 | #[cfg (test)] |
| 1271 | mod tests; |
| 1272 | |
| 1273 | // See [`unsupported_wait_status::compare_with_linux`]; |
| 1274 | #[cfg (all(test, target_os = "linux" ))] |
| 1275 | #[path = "unsupported/wait_status.rs" ] |
| 1276 | mod unsupported_wait_status; |
| 1277 | |