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