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 | |