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