Skip to main content

yash_env/system/
real.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2021 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! Implementation of system traits that actually interacts with the underlying system
18//!
19//! This module is implemented on Unix-like targets only. It provides [`RealSystem`],
20//! an implementation of system traits that interact with the underlying
21//! operating system. This implementation is intended to be used for the actual
22//! shell environment.
23
24mod errno;
25mod file_system;
26mod open_flag;
27mod resource;
28mod signal;
29
30use super::AT_FDCWD;
31use super::CaughtSignals;
32use super::Chdir;
33use super::ChildProcessStarter;
34use super::Clock;
35use super::Close;
36use super::CpuTimes;
37use super::Dir;
38use super::DirEntry;
39use super::Disposition;
40use super::Dup;
41use super::Errno;
42use super::Exec;
43use super::Exit;
44use super::Fcntl;
45use super::FdFlag;
46use super::FileType;
47use super::Fork;
48use super::Fstat;
49use super::GetCwd;
50use super::GetPid;
51use super::GetPw;
52use super::GetRlimit;
53use super::GetSigaction;
54use super::GetUid;
55use super::Gid;
56use super::IsExecutableFile;
57use super::Isatty;
58use super::Mode;
59use super::OfdAccess;
60use super::Open;
61use super::OpenFlag;
62use super::Pipe;
63use super::Read;
64use super::Result;
65use super::Seek;
66use super::Select;
67use super::SendSignal;
68use super::SetPgid;
69use super::SetRlimit;
70use super::ShellPath;
71use super::Sigaction;
72use super::Sigmask;
73use super::SigmaskOp;
74use super::Signals;
75use super::Stat;
76use super::Sysconf;
77use super::TcGetPgrp;
78use super::TcSetPgrp;
79use super::Times;
80use super::Uid;
81use super::Umask;
82use super::Wait;
83use super::Write;
84use super::resource::LimitPair;
85use super::resource::Resource;
86#[cfg(doc)]
87use crate::Env;
88use crate::io::Fd;
89use crate::job::Pid;
90use crate::job::ProcessResult;
91use crate::job::ProcessState;
92use crate::path::Path;
93use crate::path::PathBuf;
94use crate::semantics::ExitStatus;
95use crate::str::UnixStr;
96use crate::str::UnixString;
97use enumset::EnumSet;
98use libc::DIR;
99use std::convert::Infallible;
100use std::convert::TryInto;
101use std::ffi::CStr;
102use std::ffi::CString;
103use std::ffi::OsStr;
104use std::ffi::c_int;
105use std::future::ready;
106use std::io::SeekFrom;
107use std::mem::MaybeUninit;
108use std::num::NonZero;
109use std::ops::RangeInclusive;
110use std::os::unix::ffi::OsStrExt as _;
111use std::os::unix::io::IntoRawFd;
112use std::ptr::NonNull;
113use std::sync::atomic::AtomicIsize;
114use std::sync::atomic::Ordering;
115use std::sync::atomic::compiler_fence;
116use std::time::Duration;
117use std::time::Instant;
118use yash_executor::Executor;
119
120trait ErrnoIfM1: PartialEq + Sized {
121    const MINUS_1: Self;
122
123    /// Convenience function to convert a result of -1 to an `Error` with the
124    /// current `errno`.
125    ///
126    /// This function is intended to be used just after calling a function that
127    /// returns -1 on error and sets `errno` to the error number. This function
128    /// filters out the `-1` result and returns an error containing the current
129    /// `errno`.
130    fn errno_if_m1(self) -> Result<Self> {
131        if self == Self::MINUS_1 {
132            Err(Errno::last())
133        } else {
134            Ok(self)
135        }
136    }
137}
138
139impl ErrnoIfM1 for i8 {
140    const MINUS_1: Self = -1;
141}
142impl ErrnoIfM1 for i16 {
143    const MINUS_1: Self = -1;
144}
145impl ErrnoIfM1 for i32 {
146    const MINUS_1: Self = -1;
147}
148impl ErrnoIfM1 for i64 {
149    const MINUS_1: Self = -1;
150}
151impl ErrnoIfM1 for isize {
152    const MINUS_1: Self = -1;
153}
154
155/// Converts a `Duration` to a `timespec`.
156///
157/// The return value is a `MaybeUninit` because the `timespec` struct may have
158/// padding or extension fields that are not initialized by this function.
159#[must_use]
160fn to_timespec(duration: Duration) -> MaybeUninit<libc::timespec> {
161    let seconds = duration.as_secs().try_into().unwrap_or(libc::time_t::MAX);
162    let mut timespec = MaybeUninit::<libc::timespec>::uninit();
163    unsafe {
164        (&raw mut (*timespec.as_mut_ptr()).tv_sec).write(seconds);
165        (&raw mut (*timespec.as_mut_ptr()).tv_nsec).write(duration.subsec_nanos() as _);
166    }
167    timespec
168}
169
170/// Array of slots to store caught signals.
171///
172/// This array is used to store caught signals. All slots are initialized with
173/// 0, which indicates that the slot is available. When a signal is caught, the
174/// signal number is written into one of unoccupied slots.
175static CAUGHT_SIGNALS: [AtomicIsize; 8] = [const { AtomicIsize::new(0) }; 8];
176
177/// Signal catching function.
178///
179/// This function is set as a signal handler for all signals that the shell
180/// wants to catch. When a signal is caught, the signal number is written into
181/// one of the slots in [`CAUGHT_SIGNALS`].
182extern "C" fn catch_signal(signal: c_int) {
183    // This function can only perform async-signal-safe operations.
184    // Performing unsafe operations is undefined behavior!
185
186    // Find an unused slot (having a value of 0) in CAUGHT_SIGNALS and write the
187    // signal number into it.
188    // If there is a slot having a value of the signal already, do nothing.
189    // If there is no available slot, the signal will be lost!
190    let signal = signal as isize;
191    for slot in &CAUGHT_SIGNALS {
192        match slot.compare_exchange(0, signal, Ordering::Relaxed, Ordering::Relaxed) {
193            Ok(_) => break,
194            Err(slot_value) if slot_value == signal => break,
195            _ => continue,
196        }
197    }
198}
199
200fn sigaction_impl(signal: signal::Number, disposition: Option<Disposition>) -> Result<Disposition> {
201    let new_action = disposition.map(Disposition::to_sigaction);
202    let new_action_ptr = new_action
203        .as_ref()
204        .map_or(std::ptr::null(), |action| action.as_ptr());
205
206    let mut old_action = MaybeUninit::<libc::sigaction>::uninit();
207    // SAFETY: We're just creating a new raw pointer to the struct field.
208    // Nothing is dereferenced.
209    let old_mask_ptr = unsafe { &raw mut (*old_action.as_mut_ptr()).sa_mask };
210    // POSIX requires *all* sigset_t objects to be initialized before use,
211    // even if they are output parameters.
212    unsafe { libc::sigemptyset(old_mask_ptr) }.errno_if_m1()?;
213
214    unsafe { libc::sigaction(signal.as_raw(), new_action_ptr, old_action.as_mut_ptr()) }
215        .errno_if_m1()?;
216
217    // SAFETY: the `old_action` has been initialized by `sigaction`.
218    let old_disposition = unsafe { Disposition::from_sigaction(&old_action) };
219    Ok(old_disposition)
220}
221
222/// Implementation of system traits that actually interact with the underlying system
223///
224/// `RealSystem` is an empty `struct` because the underlying operating system
225/// manages the system's internal state.
226///
227/// Some trait methods implemented by `RealSystem` (such as [`SendSignal::kill`])
228/// return futures, but the returned futures do not perform asynchronous operations.
229/// Those methods block the current thread until the underlying system calls
230/// complete and then return ready futures.
231/// See also the [`system` module](super) documentation.
232#[derive(Debug)]
233pub struct RealSystem(());
234
235impl RealSystem {
236    /// Returns an instance of `RealSystem`.
237    ///
238    /// # Safety
239    ///
240    /// This function is marked `unsafe` because improper use of `RealSystem`
241    /// may lead to undefined behavior. Remember that most operations performed
242    /// on the system by [`Env`] are not thread-safe. You should never use
243    /// `RealSystem` in a multi-threaded program, and it is your responsibility
244    /// to make sure you are using only one instance of `ReadSystem` in the
245    /// process.
246    pub unsafe fn new() -> Self {
247        RealSystem(())
248    }
249
250    fn file_has_type(&self, path: &CStr, r#type: FileType) -> bool {
251        self.fstatat(AT_FDCWD, path, true)
252            .is_ok_and(|stat| stat.r#type == r#type)
253    }
254
255    // TODO Should use AT_EACCESS on all platforms
256    #[cfg(not(target_os = "redox"))]
257    fn has_execute_permission(&self, path: &CStr) -> bool {
258        (unsafe { libc::faccessat(libc::AT_FDCWD, path.as_ptr(), libc::X_OK, libc::AT_EACCESS) })
259            != -1
260    }
261    #[cfg(target_os = "redox")]
262    fn has_execute_permission(&self, path: &CStr) -> bool {
263        (unsafe { libc::access(path.as_ptr(), libc::X_OK) }) != -1
264    }
265}
266
267impl Fstat for RealSystem {
268    fn fstat(&self, fd: Fd) -> Result<Stat> {
269        let mut stat = MaybeUninit::<libc::stat>::uninit();
270        unsafe { libc::fstat(fd.0, stat.as_mut_ptr()) }.errno_if_m1()?;
271        let stat = unsafe { Stat::from_raw(&stat) };
272        Ok(stat)
273    }
274
275    fn fstatat(&self, dir_fd: Fd, path: &CStr, follow_symlinks: bool) -> Result<Stat> {
276        let flags = if follow_symlinks {
277            0
278        } else {
279            libc::AT_SYMLINK_NOFOLLOW
280        };
281
282        let mut stat = MaybeUninit::<libc::stat>::uninit();
283        unsafe { libc::fstatat(dir_fd.0, path.as_ptr(), stat.as_mut_ptr(), flags) }
284            .errno_if_m1()?;
285        let stat = unsafe { Stat::from_raw(&stat) };
286        Ok(stat)
287    }
288}
289
290impl IsExecutableFile for RealSystem {
291    fn is_executable_file(&self, path: &CStr) -> bool {
292        self.file_has_type(path, FileType::Regular) && self.has_execute_permission(path)
293    }
294}
295
296impl Pipe for RealSystem {
297    fn pipe(&self) -> Result<(Fd, Fd)> {
298        let mut fds = MaybeUninit::<[c_int; 2]>::uninit();
299        // TODO Use as_mut_ptr rather than cast when array_ptr_get is stabilized
300        unsafe { libc::pipe(fds.as_mut_ptr().cast()) }.errno_if_m1()?;
301        let fds = unsafe { fds.assume_init() };
302        Ok((Fd(fds[0]), Fd(fds[1])))
303    }
304}
305
306impl Dup for RealSystem {
307    fn dup(&self, from: Fd, to_min: Fd, flags: EnumSet<FdFlag>) -> Result<Fd> {
308        let command = if flags.contains(FdFlag::CloseOnExec) {
309            libc::F_DUPFD_CLOEXEC
310        } else {
311            libc::F_DUPFD
312        };
313        unsafe { libc::fcntl(from.0, command, to_min.0) }
314            .errno_if_m1()
315            .map(Fd)
316    }
317
318    fn dup2(&self, from: Fd, to: Fd) -> Result<Fd> {
319        loop {
320            let result = unsafe { libc::dup2(from.0, to.0) }.errno_if_m1().map(Fd);
321            if result != Err(Errno::EINTR) {
322                return result;
323            }
324        }
325    }
326}
327
328impl Open for RealSystem {
329    fn open(
330        &self,
331        path: &CStr,
332        access: OfdAccess,
333        flags: EnumSet<OpenFlag>,
334        mode: Mode,
335    ) -> Result<Fd> {
336        let mut raw_flags = access.to_real_flag().ok_or(Errno::EINVAL)?;
337        for flag in flags {
338            raw_flags |= flag.to_real_flag().ok_or(Errno::EINVAL)?;
339        }
340
341        #[cfg(not(target_os = "redox"))]
342        let mode_bits = mode.bits() as std::ffi::c_uint;
343        #[cfg(target_os = "redox")]
344        let mode_bits = mode.bits() as c_int;
345
346        unsafe { libc::open(path.as_ptr(), raw_flags, mode_bits) }
347            .errno_if_m1()
348            .map(Fd)
349    }
350
351    fn open_tmpfile(&self, parent_dir: &Path) -> Result<Fd> {
352        let parent_dir = OsStr::from_bytes(parent_dir.as_unix_str().as_bytes());
353        let file = tempfile::tempfile_in(parent_dir)
354            .map_err(|errno| Errno(errno.raw_os_error().unwrap_or(0)))?;
355        let fd = Fd(file.into_raw_fd());
356
357        // Clear the CLOEXEC flag
358        _ = self.fcntl_setfd(fd, EnumSet::empty());
359
360        Ok(fd)
361    }
362
363    fn fdopendir(&self, fd: Fd) -> Result<impl Dir + use<>> {
364        let dir = unsafe { libc::fdopendir(fd.0) };
365        let dir = NonNull::new(dir).ok_or_else(Errno::last)?;
366        Ok(RealDir(dir))
367    }
368
369    fn opendir(&self, path: &CStr) -> Result<impl Dir + use<>> {
370        let dir = unsafe { libc::opendir(path.as_ptr()) };
371        let dir = NonNull::new(dir).ok_or_else(Errno::last)?;
372        Ok(RealDir(dir))
373    }
374}
375
376impl Close for RealSystem {
377    fn close(&self, fd: Fd) -> Result<()> {
378        // TODO: Use posix_close when available
379        loop {
380            let result = unsafe { libc::close(fd.0) }.errno_if_m1().map(drop);
381            match result {
382                Err(Errno::EBADF) => return Ok(()),
383                Err(Errno::EINTR) => continue,
384                other => return other,
385            }
386        }
387    }
388}
389
390impl Fcntl for RealSystem {
391    fn ofd_access(&self, fd: Fd) -> Result<OfdAccess> {
392        let flags = unsafe { libc::fcntl(fd.0, libc::F_GETFL) }.errno_if_m1()?;
393        Ok(OfdAccess::from_real_flag(flags))
394    }
395
396    fn get_and_set_nonblocking(&self, fd: Fd, nonblocking: bool) -> Result<bool> {
397        let old_flags = unsafe { libc::fcntl(fd.0, libc::F_GETFL) }.errno_if_m1()?;
398        let new_flags = if nonblocking {
399            old_flags | libc::O_NONBLOCK
400        } else {
401            old_flags & !libc::O_NONBLOCK
402        };
403        if new_flags != old_flags {
404            unsafe { libc::fcntl(fd.0, libc::F_SETFL, new_flags) }.errno_if_m1()?;
405        }
406        let was_nonblocking = old_flags & libc::O_NONBLOCK != 0;
407        Ok(was_nonblocking)
408    }
409
410    fn fcntl_getfd(&self, fd: Fd) -> Result<EnumSet<FdFlag>> {
411        let bits = unsafe { libc::fcntl(fd.0, libc::F_GETFD) }.errno_if_m1()?;
412        let mut flags = EnumSet::empty();
413        if bits & libc::FD_CLOEXEC != 0 {
414            flags.insert(FdFlag::CloseOnExec);
415        }
416        Ok(flags)
417    }
418
419    fn fcntl_setfd(&self, fd: Fd, flags: EnumSet<FdFlag>) -> Result<()> {
420        let mut bits = 0 as c_int;
421        if flags.contains(FdFlag::CloseOnExec) {
422            bits |= libc::FD_CLOEXEC;
423        }
424        unsafe { libc::fcntl(fd.0, libc::F_SETFD, bits) }
425            .errno_if_m1()
426            .map(drop)
427    }
428}
429
430impl Read for RealSystem {
431    fn read(&self, fd: Fd, buffer: &mut [u8]) -> Result<usize> {
432        loop {
433            let result =
434                unsafe { libc::read(fd.0, buffer.as_mut_ptr().cast(), buffer.len()) }.errno_if_m1();
435            if result != Err(Errno::EINTR) {
436                return Ok(result?.try_into().unwrap());
437            }
438        }
439    }
440}
441
442impl Write for RealSystem {
443    fn write(&self, fd: Fd, buffer: &[u8]) -> Result<usize> {
444        loop {
445            let result =
446                unsafe { libc::write(fd.0, buffer.as_ptr().cast(), buffer.len()) }.errno_if_m1();
447            if result != Err(Errno::EINTR) {
448                return Ok(result?.try_into().unwrap());
449            }
450        }
451    }
452}
453
454impl Seek for RealSystem {
455    fn lseek(&self, fd: Fd, position: SeekFrom) -> Result<u64> {
456        let (offset, whence) = match position {
457            SeekFrom::Start(offset) => {
458                let offset = offset.try_into().map_err(|_| Errno::EOVERFLOW)?;
459                (offset, libc::SEEK_SET)
460            }
461            SeekFrom::End(offset) => (offset, libc::SEEK_END),
462            SeekFrom::Current(offset) => (offset, libc::SEEK_CUR),
463        };
464        let new_offset = unsafe { libc::lseek(fd.0, offset, whence) }.errno_if_m1()?;
465        Ok(new_offset.try_into().unwrap())
466    }
467}
468
469impl Umask for RealSystem {
470    fn umask(&self, new_mask: Mode) -> Mode {
471        Mode::from_bits_retain(unsafe { libc::umask(new_mask.bits()) })
472    }
473}
474
475impl GetCwd for RealSystem {
476    fn getcwd(&self) -> Result<PathBuf> {
477        // Some getcwd implementations allocate a buffer for the path if the
478        // first argument is null, but we cannot use that feature because Vec's
479        // allocator may not be compatible with the system's allocator.
480
481        // Since there is no way to know the required buffer size, we try
482        // several buffer sizes.
483        let mut buffer = Vec::<u8>::new();
484        for capacity in [1 << 10, 1 << 12, 1 << 14, 1 << 16] {
485            buffer.reserve_exact(capacity);
486
487            let result = unsafe { libc::getcwd(buffer.as_mut_ptr().cast(), capacity) };
488            if !result.is_null() {
489                // len does not include the null terminator
490                let len = unsafe { CStr::from_ptr(buffer.as_ptr().cast()) }.count_bytes();
491                unsafe { buffer.set_len(len) }
492                buffer.shrink_to_fit();
493                return Ok(PathBuf::from(UnixString::from_vec(buffer)));
494            }
495            let errno = Errno::last();
496            if errno != Errno::ERANGE {
497                return Err(errno);
498            }
499        }
500        Err(Errno::ERANGE)
501    }
502}
503
504impl Chdir for RealSystem {
505    fn chdir(&self, path: &CStr) -> Result<()> {
506        let result = unsafe { libc::chdir(path.as_ptr()) };
507        result.errno_if_m1().map(drop)
508    }
509}
510
511impl Clock for RealSystem {
512    fn now(&self) -> Instant {
513        Instant::now()
514    }
515}
516
517impl Times for RealSystem {
518    /// Returns consumed CPU times.
519    ///
520    /// This function actually uses `getrusage` rather than `times` because it
521    /// provides better resolution on many systems.
522    fn times(&self) -> Result<CpuTimes> {
523        let mut usage = MaybeUninit::<libc::rusage>::uninit();
524
525        unsafe { libc::getrusage(libc::RUSAGE_SELF, usage.as_mut_ptr()) }.errno_if_m1()?;
526        let self_user = unsafe {
527            (*usage.as_ptr()).ru_utime.tv_sec as f64
528                + (*usage.as_ptr()).ru_utime.tv_usec as f64 * 1e-6
529        };
530        let self_system = unsafe {
531            (*usage.as_ptr()).ru_stime.tv_sec as f64
532                + (*usage.as_ptr()).ru_stime.tv_usec as f64 * 1e-6
533        };
534
535        unsafe { libc::getrusage(libc::RUSAGE_CHILDREN, usage.as_mut_ptr()) }.errno_if_m1()?;
536        let children_user = unsafe {
537            (*usage.as_ptr()).ru_utime.tv_sec as f64
538                + (*usage.as_ptr()).ru_utime.tv_usec as f64 * 1e-6
539        };
540        let children_system = unsafe {
541            (*usage.as_ptr()).ru_stime.tv_sec as f64
542                + (*usage.as_ptr()).ru_stime.tv_usec as f64 * 1e-6
543        };
544
545        Ok(CpuTimes {
546            self_user,
547            self_system,
548            children_user,
549            children_system,
550        })
551    }
552}
553
554impl GetPid for RealSystem {
555    fn getsid(&self, pid: Pid) -> Result<Pid> {
556        unsafe { libc::getsid(pid.0) }.errno_if_m1().map(Pid)
557    }
558
559    fn getpid(&self) -> Pid {
560        Pid(unsafe { libc::getpid() })
561    }
562
563    fn getppid(&self) -> Pid {
564        Pid(unsafe { libc::getppid() })
565    }
566
567    fn getpgrp(&self) -> Pid {
568        Pid(unsafe { libc::getpgrp() })
569    }
570}
571
572impl SetPgid for RealSystem {
573    fn setpgid(&self, pid: Pid, pgid: Pid) -> Result<()> {
574        let result = unsafe { libc::setpgid(pid.0, pgid.0) };
575        result.errno_if_m1().map(drop)
576    }
577}
578
579const fn to_signal_number(sig: c_int) -> signal::Number {
580    signal::Number::from_raw_unchecked(NonZero::new(sig).unwrap())
581}
582
583impl Signals for RealSystem {
584    const SIGABRT: signal::Number = to_signal_number(libc::SIGABRT);
585    const SIGALRM: signal::Number = to_signal_number(libc::SIGALRM);
586    const SIGBUS: signal::Number = to_signal_number(libc::SIGBUS);
587    const SIGCHLD: signal::Number = to_signal_number(libc::SIGCHLD);
588    #[cfg(any(
589        target_os = "aix",
590        target_os = "horizon",
591        target_os = "illumos",
592        target_os = "solaris",
593    ))]
594    const SIGCLD: Option<signal::Number> = Some(to_signal_number(libc::SIGCLD));
595    #[cfg(not(any(
596        target_os = "aix",
597        target_os = "horizon",
598        target_os = "illumos",
599        target_os = "solaris",
600    )))]
601    const SIGCLD: Option<signal::Number> = None;
602    const SIGCONT: signal::Number = to_signal_number(libc::SIGCONT);
603    #[cfg(not(any(
604        target_os = "android",
605        target_os = "emscripten",
606        target_os = "fuchsia",
607        target_os = "haiku",
608        target_os = "linux",
609        target_os = "redox",
610    )))]
611    const SIGEMT: Option<signal::Number> = Some(to_signal_number(libc::SIGEMT));
612    #[cfg(any(
613        target_os = "android",
614        target_os = "emscripten",
615        target_os = "fuchsia",
616        target_os = "haiku",
617        target_os = "linux",
618        target_os = "redox",
619    ))]
620    const SIGEMT: Option<signal::Number> = None;
621    const SIGFPE: signal::Number = to_signal_number(libc::SIGFPE);
622    const SIGHUP: signal::Number = to_signal_number(libc::SIGHUP);
623    const SIGILL: signal::Number = to_signal_number(libc::SIGILL);
624    #[cfg(not(any(
625        target_os = "aix",
626        target_os = "android",
627        target_os = "emscripten",
628        target_os = "fuchsia",
629        target_os = "haiku",
630        target_os = "linux",
631        target_os = "redox",
632    )))]
633    const SIGINFO: Option<signal::Number> = Some(to_signal_number(libc::SIGINFO));
634    #[cfg(any(
635        target_os = "aix",
636        target_os = "android",
637        target_os = "emscripten",
638        target_os = "fuchsia",
639        target_os = "haiku",
640        target_os = "linux",
641        target_os = "redox",
642    ))]
643    const SIGINFO: Option<signal::Number> = None;
644    const SIGINT: signal::Number = to_signal_number(libc::SIGINT);
645    #[cfg(any(
646        target_os = "aix",
647        target_os = "android",
648        target_os = "emscripten",
649        target_os = "fuchsia",
650        target_os = "horizon",
651        target_os = "illumos",
652        target_os = "linux",
653        target_os = "nto",
654        target_os = "solaris",
655    ))]
656    const SIGIO: Option<signal::Number> = Some(to_signal_number(libc::SIGIO));
657    #[cfg(not(any(
658        target_os = "aix",
659        target_os = "android",
660        target_os = "emscripten",
661        target_os = "fuchsia",
662        target_os = "horizon",
663        target_os = "illumos",
664        target_os = "linux",
665        target_os = "nto",
666        target_os = "solaris",
667    )))]
668    const SIGIO: Option<signal::Number> = None;
669    const SIGIOT: signal::Number = to_signal_number(libc::SIGIOT);
670    const SIGKILL: signal::Number = to_signal_number(libc::SIGKILL);
671    #[cfg(target_os = "horizon")]
672    const SIGLOST: Option<signal::Number> = Some(to_signal_number(libc::SIGLOST));
673    #[cfg(not(target_os = "horizon"))]
674    const SIGLOST: Option<signal::Number> = None;
675    const SIGPIPE: signal::Number = to_signal_number(libc::SIGPIPE);
676    #[cfg(any(
677        target_os = "aix",
678        target_os = "android",
679        target_os = "emscripten",
680        target_os = "fuchsia",
681        target_os = "haiku",
682        target_os = "horizon",
683        target_os = "illumos",
684        target_os = "linux",
685        target_os = "nto",
686        target_os = "solaris",
687    ))]
688    const SIGPOLL: Option<signal::Number> = Some(to_signal_number(libc::SIGPOLL));
689    #[cfg(not(any(
690        target_os = "aix",
691        target_os = "android",
692        target_os = "emscripten",
693        target_os = "fuchsia",
694        target_os = "haiku",
695        target_os = "horizon",
696        target_os = "illumos",
697        target_os = "linux",
698        target_os = "nto",
699        target_os = "solaris",
700    )))]
701    const SIGPOLL: Option<signal::Number> = None;
702    const SIGPROF: signal::Number = to_signal_number(libc::SIGPROF);
703    #[cfg(any(
704        target_os = "aix",
705        target_os = "android",
706        target_os = "emscripten",
707        target_os = "fuchsia",
708        target_os = "illumos",
709        target_os = "linux",
710        target_os = "nto",
711        target_os = "redox",
712        target_os = "solaris",
713    ))]
714    const SIGPWR: Option<signal::Number> = Some(to_signal_number(libc::SIGPWR));
715    #[cfg(not(any(
716        target_os = "aix",
717        target_os = "android",
718        target_os = "emscripten",
719        target_os = "fuchsia",
720        target_os = "illumos",
721        target_os = "linux",
722        target_os = "nto",
723        target_os = "redox",
724        target_os = "solaris",
725    )))]
726    const SIGPWR: Option<signal::Number> = None;
727    const SIGQUIT: signal::Number = to_signal_number(libc::SIGQUIT);
728    const SIGSEGV: signal::Number = to_signal_number(libc::SIGSEGV);
729    #[cfg(all(
730        any(
731            target_os = "android",
732            target_os = "emscripten",
733            target_os = "fuchsia",
734            target_os = "linux"
735        ),
736        not(any(target_arch = "mips", target_arch = "mips64", target_arch = "sparc64"))
737    ))]
738    const SIGSTKFLT: Option<signal::Number> = Some(to_signal_number(libc::SIGSTKFLT));
739    #[cfg(not(all(
740        any(
741            target_os = "android",
742            target_os = "emscripten",
743            target_os = "fuchsia",
744            target_os = "linux"
745        ),
746        not(any(target_arch = "mips", target_arch = "mips64", target_arch = "sparc64"))
747    )))]
748    const SIGSTKFLT: Option<signal::Number> = None;
749    const SIGSTOP: signal::Number = to_signal_number(libc::SIGSTOP);
750    const SIGSYS: signal::Number = to_signal_number(libc::SIGSYS);
751    const SIGTERM: signal::Number = to_signal_number(libc::SIGTERM);
752    #[cfg(target_os = "freebsd")]
753    const SIGTHR: Option<signal::Number> = Some(to_signal_number(libc::SIGTHR));
754    #[cfg(not(target_os = "freebsd"))]
755    const SIGTHR: Option<signal::Number> = None;
756    const SIGTRAP: signal::Number = to_signal_number(libc::SIGTRAP);
757    const SIGTSTP: signal::Number = to_signal_number(libc::SIGTSTP);
758    const SIGTTIN: signal::Number = to_signal_number(libc::SIGTTIN);
759    const SIGTTOU: signal::Number = to_signal_number(libc::SIGTTOU);
760    const SIGURG: signal::Number = to_signal_number(libc::SIGURG);
761    const SIGUSR1: signal::Number = to_signal_number(libc::SIGUSR1);
762    const SIGUSR2: signal::Number = to_signal_number(libc::SIGUSR2);
763    const SIGVTALRM: signal::Number = to_signal_number(libc::SIGVTALRM);
764    const SIGWINCH: signal::Number = to_signal_number(libc::SIGWINCH);
765    const SIGXCPU: signal::Number = to_signal_number(libc::SIGXCPU);
766    const SIGXFSZ: signal::Number = to_signal_number(libc::SIGXFSZ);
767
768    fn sigrt_range(&self) -> Option<RangeInclusive<signal::Number>> {
769        let raw_range = signal::rt_range();
770        let start = signal::Number::from_raw_unchecked(NonZero::new(*raw_range.start())?);
771        let end = signal::Number::from_raw_unchecked(NonZero::new(*raw_range.end())?);
772        Some(start..=end).filter(|range| !range.is_empty())
773    }
774
775    // TODO: Implement sig2str and str2sig methods
776}
777
778impl Sigmask for RealSystem {
779    fn sigmask(
780        &self,
781        op: Option<(SigmaskOp, &[signal::Number])>,
782        old_mask: Option<&mut Vec<signal::Number>>,
783    ) -> Result<()> {
784        unsafe {
785            let (how, raw_mask) = match op {
786                None => (libc::SIG_BLOCK, None),
787                Some((op, mask)) => {
788                    let how = match op {
789                        SigmaskOp::Add => libc::SIG_BLOCK,
790                        SigmaskOp::Remove => libc::SIG_UNBLOCK,
791                        SigmaskOp::Set => libc::SIG_SETMASK,
792                    };
793
794                    let mut raw_mask = MaybeUninit::<libc::sigset_t>::uninit();
795                    libc::sigemptyset(raw_mask.as_mut_ptr()).errno_if_m1()?;
796                    for &signal in mask {
797                        libc::sigaddset(raw_mask.as_mut_ptr(), signal.as_raw()).errno_if_m1()?;
798                    }
799
800                    (how, Some(raw_mask))
801                }
802            };
803            let mut old_mask_pair = match old_mask {
804                None => None,
805                Some(old_mask) => {
806                    let mut raw_old_mask = MaybeUninit::<libc::sigset_t>::uninit();
807                    // POSIX requires *all* sigset_t objects to be initialized before use.
808                    libc::sigemptyset(raw_old_mask.as_mut_ptr()).errno_if_m1()?;
809                    Some((old_mask, raw_old_mask))
810                }
811            };
812
813            let raw_set_ptr = raw_mask
814                .as_ref()
815                .map_or(std::ptr::null(), |raw_set| raw_set.as_ptr());
816            let raw_old_set_ptr = old_mask_pair
817                .as_mut()
818                .map_or(std::ptr::null_mut(), |(_, raw_old_mask)| {
819                    raw_old_mask.as_mut_ptr()
820                });
821            let result = libc::sigprocmask(how, raw_set_ptr, raw_old_set_ptr);
822            result.errno_if_m1().map(drop)?;
823
824            if let Some((old_mask, raw_old_mask)) = old_mask_pair {
825                old_mask.clear();
826                signal::sigset_to_vec(raw_old_mask.as_ptr(), old_mask);
827            }
828
829            Ok(())
830        }
831    }
832}
833
834impl GetSigaction for RealSystem {
835    fn get_sigaction(&self, signal: signal::Number) -> Result<Disposition> {
836        sigaction_impl(signal, None)
837    }
838}
839
840impl Sigaction for RealSystem {
841    fn sigaction(&self, signal: signal::Number, handling: Disposition) -> Result<Disposition> {
842        sigaction_impl(signal, Some(handling))
843    }
844}
845
846impl CaughtSignals for RealSystem {
847    fn caught_signals(&self) -> Vec<signal::Number> {
848        let mut signals = Vec::new();
849        for slot in &CAUGHT_SIGNALS {
850            // Need a fence to ensure we examine the slots in order.
851            compiler_fence(Ordering::Acquire);
852
853            let number = slot.swap(0, Ordering::Relaxed);
854            let Some(number) = NonZero::new(number as signal::RawNumber) else {
855                // The `catch_signal` function always fills the first unused
856                // slot, so there is no more slot filled with a signal.
857                break;
858            };
859            signals.push(signal::Number::from_raw_unchecked(number));
860        }
861        signals
862    }
863}
864
865impl SendSignal for RealSystem {
866    fn kill(
867        &self,
868        target: Pid,
869        signal: Option<signal::Number>,
870    ) -> impl Future<Output = Result<()>> + use<> {
871        let raw = signal.map_or(0, signal::Number::as_raw);
872        let result = unsafe { libc::kill(target.0, raw) }.errno_if_m1().map(drop);
873        ready(result)
874    }
875
876    fn raise(&self, signal: signal::Number) -> impl Future<Output = Result<()>> + use<> {
877        let raw = signal.as_raw();
878        let result = unsafe { libc::raise(raw) }.errno_if_m1().map(drop);
879        ready(result)
880    }
881}
882
883impl Select for RealSystem {
884    fn select(
885        &self,
886        readers: &mut Vec<Fd>,
887        writers: &mut Vec<Fd>,
888        timeout: Option<Duration>,
889        signal_mask: Option<&[signal::Number]>,
890    ) -> Result<c_int> {
891        use std::ptr::{null, null_mut};
892
893        let max_fd = readers.iter().chain(writers.iter()).max();
894        let nfds = max_fd
895            .map(|fd| fd.0.checked_add(1).ok_or(Errno::EBADF))
896            .transpose()?
897            .unwrap_or(0);
898
899        fn to_raw_fd_set(fds: &[Fd]) -> MaybeUninit<libc::fd_set> {
900            let mut raw_fds = MaybeUninit::<libc::fd_set>::uninit();
901            unsafe {
902                libc::FD_ZERO(raw_fds.as_mut_ptr());
903                for fd in fds {
904                    libc::FD_SET(fd.0, raw_fds.as_mut_ptr());
905                }
906            }
907            raw_fds
908        }
909        let mut raw_readers = to_raw_fd_set(readers);
910        let mut raw_writers = to_raw_fd_set(writers);
911        let readers_ptr = raw_readers.as_mut_ptr();
912        let writers_ptr = raw_writers.as_mut_ptr();
913        let errors = null_mut();
914
915        let timeout_spec = to_timespec(timeout.unwrap_or_default());
916        let timeout_ptr = if timeout.is_some() {
917            timeout_spec.as_ptr()
918        } else {
919            null()
920        };
921
922        let mut raw_mask = MaybeUninit::<libc::sigset_t>::uninit();
923        let raw_mask_ptr = match signal_mask {
924            None => null(),
925            Some(signal_mask) => {
926                unsafe { libc::sigemptyset(raw_mask.as_mut_ptr()) }.errno_if_m1()?;
927                for &signal in signal_mask {
928                    unsafe { libc::sigaddset(raw_mask.as_mut_ptr(), signal.as_raw()) }
929                        .errno_if_m1()?;
930                }
931                raw_mask.as_ptr()
932            }
933        };
934
935        let count = unsafe {
936            libc::pselect(
937                nfds,
938                readers_ptr,
939                writers_ptr,
940                errors,
941                timeout_ptr,
942                raw_mask_ptr,
943            )
944        }
945        .errno_if_m1()?;
946
947        readers.retain(|fd| unsafe { libc::FD_ISSET(fd.0, readers_ptr) });
948        writers.retain(|fd| unsafe { libc::FD_ISSET(fd.0, writers_ptr) });
949
950        Ok(count)
951    }
952}
953
954impl Isatty for RealSystem {
955    fn isatty(&self, fd: Fd) -> bool {
956        (unsafe { libc::isatty(fd.0) } != 0)
957    }
958}
959
960impl TcGetPgrp for RealSystem {
961    fn tcgetpgrp(&self, fd: Fd) -> Result<Pid> {
962        unsafe { libc::tcgetpgrp(fd.0) }.errno_if_m1().map(Pid)
963    }
964}
965
966impl TcSetPgrp for RealSystem {
967    fn tcsetpgrp(&self, fd: Fd, pgid: Pid) -> impl Future<Output = Result<()>> + use<> {
968        let result = unsafe { libc::tcsetpgrp(fd.0, pgid.0) };
969        ready(result.errno_if_m1().map(drop))
970    }
971}
972
973impl Fork for RealSystem {
974    /// Creates a new child process.
975    ///
976    /// This implementation calls the `fork` system call and returns both in the
977    /// parent and child process. In the parent, the returned
978    /// `ChildProcessStarter` ignores any arguments and returns the child
979    /// process ID. In the child, the starter runs the task and exits the
980    /// process.
981    fn new_child_process(&self) -> Result<ChildProcessStarter<Self>> {
982        let raw_pid = unsafe { libc::fork() }.errno_if_m1()?;
983        if raw_pid != 0 {
984            // Parent process
985            return Ok(Box::new(move |_env, _task| Pid(raw_pid)));
986        }
987
988        // Child process
989        Ok(Box::new(|env, task| {
990            let system = env.system.clone();
991            // Here we create a new executor to run the task. This makes sure that any
992            // other concurrent tasks in the parent process do not interfere with the
993            // child process.
994            let executor = Executor::new();
995            let task = Box::pin(async move { match task(env).await {} });
996            // SAFETY: We never create new threads in the whole process, so wakers are
997            // never shared between threads.
998            unsafe { executor.spawn_pinned(task) }
999            loop {
1000                executor.run_until_stalled();
1001                system.select(false).ok();
1002            }
1003        }))
1004    }
1005}
1006
1007impl Wait for RealSystem {
1008    fn wait(&self, target: Pid) -> Result<Option<(Pid, ProcessState)>> {
1009        let mut status = 0;
1010        let options = libc::WUNTRACED | libc::WCONTINUED | libc::WNOHANG;
1011        match unsafe { libc::waitpid(target.0, &mut status, options) } {
1012            -1 => Err(Errno::last()),
1013            0 => Ok(None),
1014            pid => {
1015                let state = if libc::WIFCONTINUED(status) {
1016                    ProcessState::Running
1017                } else if libc::WIFEXITED(status) {
1018                    let exit_status = libc::WEXITSTATUS(status);
1019                    ProcessState::exited(exit_status)
1020                } else if libc::WIFSIGNALED(status) {
1021                    let signal = libc::WTERMSIG(status);
1022                    let core_dump = libc::WCOREDUMP(status);
1023                    // SAFETY: The signal number is always a valid signal number, which is non-zero.
1024                    let raw_number = unsafe { NonZero::new_unchecked(signal) };
1025                    let signal = signal::Number::from_raw_unchecked(raw_number);
1026                    let process_result = ProcessResult::Signaled { signal, core_dump };
1027                    process_result.into()
1028                } else if libc::WIFSTOPPED(status) {
1029                    let signal = libc::WSTOPSIG(status);
1030                    // SAFETY: The signal number is always a valid signal number, which is non-zero.
1031                    let raw_number = unsafe { NonZero::new_unchecked(signal) };
1032                    let signal = signal::Number::from_raw_unchecked(raw_number);
1033                    ProcessState::stopped(signal)
1034                } else {
1035                    unreachable!()
1036                };
1037                Ok(Some((Pid(pid), state)))
1038            }
1039        }
1040    }
1041}
1042
1043impl Exec for RealSystem {
1044    fn execve(
1045        &self,
1046        path: &CStr,
1047        args: &[CString],
1048        envs: &[CString],
1049    ) -> impl Future<Output = Result<Infallible>> + use<> {
1050        fn to_pointer_array<S: AsRef<CStr>>(strs: &[S]) -> Vec<*const libc::c_char> {
1051            strs.iter()
1052                .map(|s| s.as_ref().as_ptr())
1053                .chain(std::iter::once(std::ptr::null()))
1054                .collect()
1055        }
1056        // TODO Uncomment when upgrading to libc 1.0
1057        // // This function makes mutable char pointers from immutable string
1058        // // slices since `execve` requires mutable pointers.
1059        // fn to_pointer_array<S: AsRef<CStr>>(strs: &[S]) -> Vec<*mut libc::c_char> {
1060        //     strs.iter()
1061        //         .map(|s| s.as_ref().as_ptr().cast_mut())
1062        //         .chain(std::iter::once(std::ptr::null_mut()))
1063        //         .collect()
1064        // }
1065
1066        let args = to_pointer_array(args);
1067        let envs = to_pointer_array(envs);
1068        loop {
1069            let _ = unsafe { libc::execve(path.as_ptr(), args.as_ptr(), envs.as_ptr()) };
1070            let errno = Errno::last();
1071            if errno != Errno::EINTR {
1072                return ready(Err(errno));
1073            }
1074        }
1075    }
1076}
1077
1078impl Exit for RealSystem {
1079    #[allow(unreachable_code)]
1080    fn exit(&self, exit_status: ExitStatus) -> impl Future<Output = Infallible> + use<> {
1081        ready(unsafe { libc::_exit(exit_status.0) })
1082    }
1083}
1084
1085impl GetUid for RealSystem {
1086    fn getuid(&self) -> Uid {
1087        Uid(unsafe { libc::getuid() })
1088    }
1089
1090    fn geteuid(&self) -> Uid {
1091        Uid(unsafe { libc::geteuid() })
1092    }
1093
1094    fn getgid(&self) -> Gid {
1095        Gid(unsafe { libc::getgid() })
1096    }
1097
1098    fn getegid(&self) -> Gid {
1099        Gid(unsafe { libc::getegid() })
1100    }
1101}
1102
1103impl GetPw for RealSystem {
1104    fn getpwnam_dir(&self, name: &CStr) -> Result<Option<PathBuf>> {
1105        Errno::clear();
1106        let passwd = unsafe { libc::getpwnam(name.as_ptr()) };
1107        if passwd.is_null() {
1108            let errno = Errno::last();
1109            return if errno == Errno::NO_ERROR {
1110                Ok(None)
1111            } else {
1112                Err(errno)
1113            };
1114        }
1115
1116        let dir = unsafe { CStr::from_ptr((*passwd).pw_dir) };
1117        Ok(Some(UnixString::from_vec(dir.to_bytes().to_vec()).into()))
1118    }
1119}
1120
1121impl Sysconf for RealSystem {
1122    fn confstr_path(&self) -> Result<UnixString> {
1123        // TODO Support other platforms
1124        #[cfg(any(
1125            target_os = "linux",
1126            target_os = "macos",
1127            target_os = "ios",
1128            target_os = "tvos",
1129            target_os = "watchos"
1130        ))]
1131        unsafe {
1132            let size = libc::confstr(libc::_CS_PATH, std::ptr::null_mut(), 0);
1133            if size == 0 {
1134                return Err(Errno::last());
1135            }
1136            let mut buffer = Vec::<u8>::with_capacity(size);
1137            let final_size = libc::confstr(libc::_CS_PATH, buffer.as_mut_ptr() as *mut _, size);
1138            if final_size == 0 {
1139                return Err(Errno::last());
1140            }
1141            if final_size > size {
1142                return Err(Errno::ERANGE);
1143            }
1144            buffer.set_len(final_size - 1); // The last byte is a null terminator.
1145            return Ok(UnixString::from_vec(buffer));
1146        }
1147
1148        #[allow(unreachable_code)]
1149        Err(Errno::ENOSYS)
1150    }
1151}
1152
1153impl ShellPath for RealSystem {
1154    /// Returns the path to the shell.
1155    ///
1156    /// On Linux, this function returns `/proc/self/exe`. On other platforms, it
1157    /// searches for an executable `sh` from the default PATH returned by
1158    /// [`confstr_path`](Self::confstr_path).
1159    fn shell_path(&self) -> CString {
1160        #[cfg(any(target_os = "linux", target_os = "android"))]
1161        if self.is_executable_file(c"/proc/self/exe") {
1162            return c"/proc/self/exe".to_owned();
1163        }
1164        // TODO Add optimization for other targets
1165
1166        // Find an executable "sh" from the default PATH
1167        if let Ok(path) = self.confstr_path() {
1168            if let Some(full_path) = path
1169                .as_bytes()
1170                .split(|b| *b == b':')
1171                .map(|dir| Path::new(UnixStr::from_bytes(dir)).join("sh"))
1172                .filter(|full_path| full_path.is_absolute())
1173                .filter_map(|full_path| CString::new(full_path.into_unix_string().into_vec()).ok())
1174                .find(|full_path| self.is_executable_file(full_path))
1175            {
1176                return full_path;
1177            }
1178        }
1179
1180        // The last resort
1181        c"/bin/sh".to_owned()
1182    }
1183}
1184
1185impl GetRlimit for RealSystem {
1186    fn getrlimit(&self, resource: Resource) -> Result<LimitPair> {
1187        let raw_resource = resource.as_raw_type().ok_or(Errno::EINVAL)?;
1188
1189        let mut limits = MaybeUninit::<libc::rlimit>::uninit();
1190        unsafe { libc::getrlimit(raw_resource as _, limits.as_mut_ptr()) }.errno_if_m1()?;
1191
1192        // SAFETY: These two fields of `limits` have been initialized by `getrlimit`.
1193        // (But that does not mean *all* fields are initialized,
1194        // so we cannot use `assume_init` here.)
1195        Ok(LimitPair {
1196            soft: unsafe { (*limits.as_ptr()).rlim_cur },
1197            hard: unsafe { (*limits.as_ptr()).rlim_max },
1198        })
1199    }
1200}
1201
1202impl SetRlimit for RealSystem {
1203    fn setrlimit(&self, resource: Resource, limits: LimitPair) -> Result<()> {
1204        let raw_resource = resource.as_raw_type().ok_or(Errno::EINVAL)?;
1205
1206        let mut rlimit = MaybeUninit::<libc::rlimit>::uninit();
1207        unsafe {
1208            (&raw mut (*rlimit.as_mut_ptr()).rlim_cur).write(limits.soft);
1209            (&raw mut (*rlimit.as_mut_ptr()).rlim_max).write(limits.hard);
1210        }
1211
1212        unsafe { libc::setrlimit(raw_resource as _, rlimit.as_ptr()) }.errno_if_m1()?;
1213        Ok(())
1214    }
1215}
1216
1217/// Implementor of [`Dir`] that iterates on a real directory
1218#[derive(Debug)]
1219struct RealDir(NonNull<DIR>);
1220
1221impl Drop for RealDir {
1222    fn drop(&mut self) {
1223        unsafe {
1224            libc::closedir(self.0.as_ptr());
1225        }
1226    }
1227}
1228
1229impl Dir for RealDir {
1230    fn next(&mut self) -> Result<Option<DirEntry<'_>>> {
1231        Errno::clear();
1232        let entry = unsafe { libc::readdir(self.0.as_ptr()) };
1233        let errno = Errno::last();
1234        if entry.is_null() {
1235            if errno == Errno::NO_ERROR {
1236                Ok(None)
1237            } else {
1238                Err(errno)
1239            }
1240        } else {
1241            // TODO Use as_ptr rather than cast when array_ptr_get is stabilized
1242            let name = unsafe { CStr::from_ptr((&raw const (*entry).d_name).cast()) };
1243            let name = UnixStr::from_bytes(name.to_bytes());
1244            Ok(Some(DirEntry { name }))
1245        }
1246    }
1247}
1248
1249#[cfg(test)]
1250mod tests {
1251    use super::*;
1252
1253    #[test]
1254    fn real_system_directory_entries() {
1255        let system = unsafe { RealSystem::new() };
1256        let mut dir = system.opendir(c".").unwrap();
1257        let mut count = 0;
1258        while dir.next().unwrap().is_some() {
1259            count += 1;
1260        }
1261        assert!(count > 0);
1262    }
1263
1264    // This test depends on static variables.
1265    #[test]
1266    fn real_system_caught_signals() {
1267        unsafe {
1268            let system = RealSystem::new();
1269            let result = system.caught_signals();
1270            assert_eq!(result, []);
1271
1272            catch_signal(libc::SIGINT);
1273            catch_signal(libc::SIGTERM);
1274            catch_signal(libc::SIGTERM);
1275            catch_signal(libc::SIGCHLD);
1276
1277            let sigint = signal::Number::from_raw_unchecked(NonZero::new(libc::SIGINT).unwrap());
1278            let sigterm = signal::Number::from_raw_unchecked(NonZero::new(libc::SIGTERM).unwrap());
1279            let sigchld = signal::Number::from_raw_unchecked(NonZero::new(libc::SIGCHLD).unwrap());
1280
1281            let result = system.caught_signals();
1282            assert_eq!(result, [sigint, sigterm, sigchld]);
1283            let result = system.caught_signals();
1284            assert_eq!(result, []);
1285        }
1286    }
1287}