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