yash_env/system/virtual/
process.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//! Processes in a virtual system.
18
19use super::Disposition;
20use super::Gid;
21use super::Mode;
22use super::SigmaskOp;
23use super::Uid;
24use super::io::FdBody;
25use super::signal::{self, SignalEffect};
26use crate::io::Fd;
27use crate::job::Pid;
28use crate::job::ProcessResult;
29use crate::job::ProcessState;
30use crate::path::Path;
31use crate::path::PathBuf;
32use crate::system::SelectSystem;
33use crate::system::resource::INFINITY;
34use crate::system::resource::LimitPair;
35use crate::system::resource::Resource;
36use crate::system::r#virtual::VirtualSystem;
37use std::cell::Cell;
38use std::cell::RefCell;
39use std::collections::BTreeMap;
40use std::collections::BTreeSet;
41use std::collections::HashMap;
42use std::ffi::CString;
43use std::fmt::Debug;
44use std::ops::BitOr;
45use std::ops::BitOrAssign;
46use std::rc::Weak;
47use std::task::Waker;
48
49/// Process in a virtual system
50#[derive(Clone, Debug)]
51pub struct Process {
52    /// Process ID of the parent process
53    pub(crate) ppid: Pid,
54
55    /// Process group ID of this process
56    pub(crate) pgid: Pid,
57
58    /// Real user ID of this process
59    uid: Uid,
60
61    /// Effective user ID of this process
62    euid: Uid,
63
64    /// Real group ID of this process
65    gid: Gid,
66
67    /// Effective group ID of this process
68    egid: Gid,
69
70    /// Set of file descriptors open in this process
71    pub(crate) fds: BTreeMap<Fd, FdBody>,
72
73    /// File creation mask
74    pub(crate) umask: Mode,
75
76    /// Working directory path
77    pub(crate) cwd: PathBuf,
78
79    /// Execution state of the process
80    pub(crate) state: ProcessState,
81
82    /// True when `state` has changed but not yet reported to the parent
83    /// process.
84    ///
85    /// The change of `state` is reported when the parent `wait`s for this
86    /// process.
87    state_has_changed: bool,
88
89    /// Wakers waiting for the state of this process to change to `Running`
90    resumption_awaiters: Vec<Weak<Cell<Option<Waker>>>>,
91
92    /// Current signal dispositions
93    ///
94    /// For signals not contained in this hash map, the default disposition is
95    /// assumed.
96    dispositions: HashMap<signal::Number, Disposition>,
97
98    /// Set of blocked signals
99    blocked_signals: BTreeSet<signal::Number>,
100
101    /// Set of pending signals
102    pending_signals: BTreeSet<signal::Number>,
103
104    /// List of signals that have been delivered and caught
105    pub(crate) caught_signals: Vec<signal::Number>,
106
107    /// Limits for system resources
108    pub(crate) resource_limits: HashMap<Resource, LimitPair>,
109
110    /// Weak reference to the `SelectSystem` for this process
111    ///
112    /// This weak reference is empty for the initial process of a
113    /// `VirtualSystem`.  When a new child process is created, a weak reference
114    /// to the `SelectSystem` for the child is set.
115    pub(crate) selector: Weak<RefCell<SelectSystem<VirtualSystem>>>,
116
117    /// Copy of arguments passed to [`execve`](crate::System::execve)
118    pub(crate) last_exec: Option<(CString, Vec<CString>, Vec<CString>)>,
119}
120
121/// Finds the minimum available FD.
122///
123/// The returned FD is the minimum that is equal to or greater than `min` and
124/// not included in `existings`. Items of `existings` must be sorted.
125fn min_unused_fd<'a, I: IntoIterator<Item = &'a Fd>>(min: Fd, existings: I) -> Fd {
126    let candidates = (min.0..).map(Fd);
127    let rejections = existings
128        .into_iter()
129        .skip_while(|fd| **fd < min)
130        .map(Some)
131        .chain(std::iter::repeat(None));
132    candidates
133        .zip(rejections)
134        .skip_while(|(candidate, rejection)| Some(candidate) == *rejection)
135        .map(|(candidate, _rejection)| candidate)
136        .next()
137        .unwrap()
138}
139
140impl Process {
141    /// Creates a new running process.
142    pub fn with_parent_and_group(ppid: Pid, pgid: Pid) -> Process {
143        Process {
144            ppid,
145            pgid,
146            uid: Uid(1),
147            euid: Uid(1),
148            gid: Gid(1),
149            egid: Gid(1),
150            fds: BTreeMap::new(),
151            umask: Mode::default(),
152            cwd: PathBuf::new(),
153            state: ProcessState::Running,
154            state_has_changed: false,
155            resumption_awaiters: Vec::new(),
156            dispositions: HashMap::new(),
157            blocked_signals: BTreeSet::new(),
158            pending_signals: BTreeSet::new(),
159            caught_signals: Vec::new(),
160            resource_limits: HashMap::new(),
161            selector: Weak::new(),
162            last_exec: None,
163        }
164    }
165
166    /// Creates a new running process as a child of the given parent.
167    ///
168    /// Some part of the parent process state is copied to the new process.
169    pub fn fork_from(ppid: Pid, parent: &Process) -> Process {
170        let mut child = Self::with_parent_and_group(ppid, parent.pgid);
171        child.uid = parent.uid;
172        child.euid = parent.euid;
173        child.gid = parent.gid;
174        child.egid = parent.egid;
175        child.fds = parent.fds.clone();
176        child.dispositions.clone_from(&parent.dispositions);
177        child.blocked_signals.clone_from(&parent.blocked_signals);
178        child.pending_signals = BTreeSet::new();
179        child
180    }
181
182    /// Returns the process ID of the parent process.
183    #[inline(always)]
184    #[must_use]
185    pub fn ppid(&self) -> Pid {
186        self.ppid
187    }
188
189    /// Returns the process group ID of this process.
190    #[inline(always)]
191    #[must_use]
192    pub fn pgid(&self) -> Pid {
193        self.pgid
194    }
195
196    /// Returns the real user ID of this process.
197    #[inline(always)]
198    #[must_use]
199    pub fn uid(&self) -> Uid {
200        self.uid
201    }
202
203    /// Sets the real user ID of this process.
204    #[inline(always)]
205    pub fn set_uid(&mut self, uid: Uid) {
206        self.uid = uid;
207    }
208
209    /// Returns the effective user ID of this process.
210    #[inline(always)]
211    #[must_use]
212    pub fn euid(&self) -> Uid {
213        self.euid
214    }
215
216    /// Sets the effective user ID of this process.
217    #[inline(always)]
218    pub fn set_euid(&mut self, euid: Uid) {
219        self.euid = euid;
220    }
221
222    /// Returns the real group ID of this process.
223    #[inline(always)]
224    #[must_use]
225    pub fn gid(&self) -> Gid {
226        self.gid
227    }
228
229    /// Sets the real group ID of this process.
230    #[inline(always)]
231    pub fn set_gid(&mut self, gid: Gid) {
232        self.gid = gid;
233    }
234
235    /// Returns the effective group ID of this process.
236    #[inline(always)]
237    #[must_use]
238    pub fn egid(&self) -> Gid {
239        self.egid
240    }
241
242    /// Sets the effective group ID of this process.
243    #[inline(always)]
244    pub fn set_egid(&mut self, egid: Gid) {
245        self.egid = egid;
246    }
247
248    /// Returns FDs open in this process.
249    #[inline(always)]
250    #[must_use]
251    pub fn fds(&self) -> &BTreeMap<Fd, FdBody> {
252        &self.fds
253    }
254
255    /// Returns the body for the given FD.
256    #[inline]
257    #[must_use]
258    pub fn get_fd(&self, fd: Fd) -> Option<&FdBody> {
259        self.fds.get(&fd)
260    }
261
262    /// Returns the body for the given FD.
263    #[inline]
264    #[must_use]
265    pub fn get_fd_mut(&mut self, fd: Fd) -> Option<&mut FdBody> {
266        self.fds.get_mut(&fd)
267    }
268
269    /// Assigns the given FD to the body.
270    ///
271    /// If successful, returns an `Ok` value containing the previous body for
272    /// the FD. If the FD is equal to or greater than the current soft limit for
273    /// `Resource::NOFILE`, returns `Err(body)`.
274    pub fn set_fd(&mut self, fd: Fd, body: FdBody) -> Result<Option<FdBody>, FdBody> {
275        let limit = self
276            .resource_limits
277            .get(&Resource::NOFILE)
278            .map(|l| l.soft)
279            .unwrap_or(INFINITY);
280
281        #[allow(clippy::unnecessary_cast)]
282        if limit == INFINITY || (fd.0 as u64) < limit as u64 {
283            Ok(self.fds.insert(fd, body))
284        } else {
285            Err(body)
286        }
287    }
288
289    /// Assigns a new FD to the given body.
290    ///
291    /// The new FD will be the minimum unused FD equal to or greater than
292    /// `min_fd`.
293    ///
294    /// If successful, the new FD is returned in `Ok`.
295    /// If no more FD can be opened, returns `Err(body)`.
296    pub fn open_fd_ge(&mut self, min_fd: Fd, body: FdBody) -> Result<Fd, FdBody> {
297        let fd = min_unused_fd(min_fd, self.fds.keys());
298        let old_body = self.set_fd(fd, body)?;
299        debug_assert_eq!(old_body, None);
300        Ok(fd)
301    }
302
303    /// Assigns a new FD to the given body.
304    ///
305    /// The new FD will be the minimum unused FD, which will be returned as an
306    /// `Ok` value.
307    ///
308    /// If no more FD can be opened, returns `Err(body)`.
309    pub fn open_fd(&mut self, body: FdBody) -> Result<Fd, FdBody> {
310        self.open_fd_ge(Fd(0), body)
311    }
312
313    /// Removes the FD body for the given FD.
314    pub fn close_fd(&mut self, fd: Fd) -> Option<FdBody> {
315        self.fds.remove(&fd)
316    }
317
318    /// Removes all FD bodies in this process.
319    pub fn close_fds(&mut self) {
320        self.fds.clear();
321    }
322
323    /// Returns the working directory path.
324    pub fn getcwd(&self) -> &Path {
325        &self.cwd
326    }
327
328    /// Changes the working directory.
329    ///
330    /// This function does not check if the directory exists and is accessible.
331    pub fn chdir(&mut self, path: PathBuf) {
332        self.cwd = path
333    }
334
335    /// Registers a waker that will be woken up when this process resumes.
336    ///
337    /// The given waker will be woken up when this process is resumed by
338    /// [`set_state`](Self::set_state) or [`raise_signal`](Self::raise_signal).
339    /// A strong reference to the waker must be held by the caller until the
340    /// waker is woken up, when the waker is consumed and the `Cell` content is
341    /// set to `None`.
342    ///
343    /// This function does nothing if the process is not stopped.
344    pub fn wake_on_resumption(&mut self, waker: Weak<Cell<Option<Waker>>>) {
345        if self.state.is_stopped() {
346            self.resumption_awaiters.push(waker);
347        }
348    }
349
350    /// Returns the process state.
351    #[inline(always)]
352    #[must_use]
353    pub fn state(&self) -> ProcessState {
354        self.state
355    }
356
357    /// Sets the state of this process.
358    ///
359    /// If the new state is `Exited` or `Signaled`, all file descriptors in this
360    /// process are closed.
361    ///
362    /// This function returns whether the state did change. If true, the
363    /// [`state_has_changed`](Self::state_has_changed) flag is set and the
364    /// caller must notify the state change by sending `SIGCHLD` to the parent
365    /// process.
366    #[must_use = "You must send SIGCHLD to the parent if set_state returns true"]
367    pub fn set_state(&mut self, state: ProcessState) -> bool {
368        let old_state = std::mem::replace(&mut self.state, state);
369
370        if old_state == state {
371            false
372        } else {
373            match state {
374                ProcessState::Running => {
375                    for weak in self.resumption_awaiters.drain(..) {
376                        if let Some(strong) = weak.upgrade() {
377                            if let Some(waker) = strong.take() {
378                                waker.wake();
379                            }
380                        }
381                    }
382                }
383                ProcessState::Halted(result) => {
384                    if !result.is_stopped() {
385                        self.close_fds()
386                    }
387                }
388            }
389            self.state_has_changed = true;
390            true
391        }
392    }
393
394    /// Returns true if a new state has been [set](Self::set_state) but not yet
395    /// [taken](Self::take_state).
396    #[must_use]
397    pub fn state_has_changed(&self) -> bool {
398        self.state_has_changed
399    }
400
401    /// Returns the process state and clears the
402    /// [`state_has_changed`](Self::state_has_changed) flag.
403    pub fn take_state(&mut self) -> ProcessState {
404        self.state_has_changed = false;
405        self.state
406    }
407
408    /// Returns the currently blocked signals.
409    pub fn blocked_signals(&self) -> &BTreeSet<signal::Number> {
410        &self.blocked_signals
411    }
412
413    /// Returns the currently pending signals.
414    ///
415    /// A signal is pending when it has been raised but not yet delivered
416    /// because it is being blocked.
417    pub fn pending_signals(&self) -> &BTreeSet<signal::Number> {
418        &self.pending_signals
419    }
420
421    /// Updates the signal blocking mask for this process.
422    ///
423    /// If this function unblocks a signal, any pending signal is delivered.
424    ///
425    /// If the signal changes the execution state of the process, this function
426    /// returns a `SignalResult` with `process_state_changed` being `true`. In
427    /// that case, the caller must send a SIGCHLD to the parent process of this
428    /// process.
429    #[must_use = "send SIGCHLD if process state has changed"]
430    pub fn block_signals(&mut self, how: SigmaskOp, signals: &[signal::Number]) -> SignalResult {
431        match how {
432            SigmaskOp::Set => self.blocked_signals = signals.iter().copied().collect(),
433            SigmaskOp::Add => self.blocked_signals.extend(signals),
434            SigmaskOp::Remove => {
435                for signal in signals {
436                    self.blocked_signals.remove(signal);
437                }
438            }
439        }
440
441        let signals_to_deliver = self.pending_signals.difference(&self.blocked_signals);
442        let signals_to_deliver = signals_to_deliver.copied().collect::<Vec<signal::Number>>();
443        let mut result = SignalResult::default();
444        for signal in signals_to_deliver {
445            self.pending_signals.remove(&signal);
446            result |= self.deliver_signal(signal);
447        }
448        result
449    }
450
451    /// Returns the current disposition for a signal.
452    ///
453    /// If no disposition is set for the signal, the default disposition is
454    /// returned.
455    pub fn disposition(&self, number: signal::Number) -> Disposition {
456        self.dispositions.get(&number).copied().unwrap_or_default()
457    }
458
459    /// Gets and sets the disposition for a signal.
460    ///
461    /// This function sets the disposition to the given value and returns the
462    /// previous disposition.
463    pub fn set_disposition(
464        &mut self,
465        number: signal::Number,
466        disposition: Disposition,
467    ) -> Disposition {
468        let old_disposition = self.dispositions.insert(number, disposition);
469        old_disposition.unwrap_or_default()
470    }
471
472    /// Delivers a signal to this process.
473    ///
474    /// The action taken on the delivery depends on the current signal
475    /// disposition for the signal.
476    ///
477    /// If the signal changes the execution state of the process, this function
478    /// returns a `SignalResult` with `process_state_changed` being `true`. In
479    /// that case, the caller must send a SIGCHLD to the parent process of this
480    /// process.
481    #[must_use = "send SIGCHLD if process state has changed"]
482    fn deliver_signal(&mut self, signal: signal::Number) -> SignalResult {
483        let disposition = if signal == signal::SIGKILL || signal == signal::SIGSTOP {
484            Disposition::Default
485        } else {
486            self.disposition(signal)
487        };
488
489        match disposition {
490            Disposition::Default => {
491                let name = signal::Name::try_from_raw_virtual(signal.as_raw())
492                    .unwrap_or(signal::Name::Sys);
493                let process_state_changed = match SignalEffect::of(name) {
494                    SignalEffect::None | SignalEffect::Resume => false,
495                    SignalEffect::Terminate { core_dump } => {
496                        let result = ProcessResult::Signaled { signal, core_dump };
497                        self.set_state(ProcessState::Halted(result))
498                    }
499                    SignalEffect::Suspend => self.set_state(ProcessState::stopped(signal)),
500                };
501                SignalResult {
502                    delivered: true,
503                    caught: false,
504                    process_state_changed,
505                }
506            }
507            Disposition::Ignore => SignalResult {
508                delivered: true,
509                caught: false,
510                process_state_changed: false,
511            },
512            Disposition::Catch => {
513                self.caught_signals.push(signal);
514                SignalResult {
515                    delivered: true,
516                    caught: true,
517                    process_state_changed: false,
518                }
519            }
520        }
521    }
522
523    /// Sends a signal to this process.
524    ///
525    /// If the signal is being blocked, it will remain pending. Otherwise, it is
526    /// immediately delivered.
527    ///
528    /// If the signal changes the execution state of the process, this function
529    /// returns a `SignalResult` with `process_state_changed` being `true`. In
530    /// that case, the caller must send a SIGCHLD to the parent process of this
531    /// process.
532    #[must_use = "send SIGCHLD if process state has changed"]
533    pub fn raise_signal(&mut self, signal: signal::Number) -> SignalResult {
534        let process_state_changed =
535            signal == signal::SIGCONT && self.set_state(ProcessState::Running);
536
537        let mut result = if signal != signal::SIGKILL
538            && signal != signal::SIGSTOP
539            && self.blocked_signals().contains(&signal)
540        {
541            self.pending_signals.insert(signal);
542            SignalResult::default()
543        } else {
544            self.deliver_signal(signal)
545        };
546
547        result.process_state_changed |= process_state_changed;
548        result
549    }
550
551    /// Returns the arguments to the last call to
552    /// [`execve`](crate::system::Exec::execve) on this process.
553    #[inline(always)]
554    #[must_use]
555    pub fn last_exec(&self) -> &Option<(CString, Vec<CString>, Vec<CString>)> {
556        &self.last_exec
557    }
558}
559
560/// Result of operations that may deliver a signal to a process.
561#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
562#[non_exhaustive]
563pub struct SignalResult {
564    /// Whether the signal was delivered to the target process.
565    pub delivered: bool,
566
567    /// Whether the delivered signal was caught by a signal handler.
568    pub caught: bool,
569
570    /// Whether the signal changed the execution status of the target process.
571    ///
572    /// This flag is true when the process was terminated, suspended, or resumed.
573    pub process_state_changed: bool,
574}
575
576impl BitOr for SignalResult {
577    type Output = Self;
578    fn bitor(self, rhs: Self) -> Self::Output {
579        Self {
580            delivered: self.delivered | rhs.delivered,
581            caught: self.caught | rhs.caught,
582            process_state_changed: self.process_state_changed | rhs.process_state_changed,
583        }
584    }
585}
586
587impl BitOrAssign for SignalResult {
588    fn bitor_assign(&mut self, rhs: Self) {
589        *self = *self | rhs;
590    }
591}
592
593#[cfg(test)]
594mod tests {
595    use super::*;
596    use crate::system::r#virtual::file_system::{FileBody, Inode, Mode};
597    use crate::system::r#virtual::io::OpenFileDescription;
598    use enumset::EnumSet;
599    use futures_util::FutureExt;
600    use futures_util::task::LocalSpawnExt;
601    use std::collections::VecDeque;
602    use std::future::poll_fn;
603    use std::rc::Rc;
604    use std::task::Poll;
605
606    #[test]
607    fn min_unused_fd_for_various_arguments() {
608        assert_eq!(min_unused_fd(Fd(0), []), Fd(0));
609        assert_eq!(min_unused_fd(Fd(0), [&Fd(1)]), Fd(0));
610        assert_eq!(min_unused_fd(Fd(0), [&Fd(3), &Fd(4)]), Fd(0));
611        assert_eq!(min_unused_fd(Fd(0), [&Fd(0)]), Fd(1));
612        assert_eq!(min_unused_fd(Fd(0), [&Fd(0), &Fd(2)]), Fd(1));
613        assert_eq!(min_unused_fd(Fd(0), [&Fd(0), &Fd(1)]), Fd(2));
614        assert_eq!(min_unused_fd(Fd(0), [&Fd(0), &Fd(1), &Fd(4)]), Fd(2));
615        assert_eq!(min_unused_fd(Fd(0), [&Fd(0), &Fd(1), &Fd(2)]), Fd(3));
616
617        assert_eq!(min_unused_fd(Fd(1), []), Fd(1));
618        assert_eq!(min_unused_fd(Fd(1), [&Fd(1)]), Fd(2));
619        assert_eq!(min_unused_fd(Fd(1), [&Fd(2)]), Fd(1));
620
621        assert_eq!(min_unused_fd(Fd(1), [&Fd(1), &Fd(3), &Fd(4)]), Fd(2));
622        assert_eq!(min_unused_fd(Fd(2), [&Fd(1), &Fd(3), &Fd(4)]), Fd(2));
623        assert_eq!(min_unused_fd(Fd(3), [&Fd(1), &Fd(3), &Fd(4)]), Fd(5));
624        assert_eq!(min_unused_fd(Fd(4), [&Fd(1), &Fd(3), &Fd(4)]), Fd(5));
625        assert_eq!(min_unused_fd(Fd(5), [&Fd(1), &Fd(3), &Fd(4)]), Fd(5));
626        assert_eq!(min_unused_fd(Fd(6), [&Fd(1), &Fd(3), &Fd(4)]), Fd(6));
627    }
628
629    fn process_with_pipe() -> (Process, Fd, Fd) {
630        let mut process = Process::with_parent_and_group(Pid(10), Pid(11));
631
632        let file = Rc::new(RefCell::new(Inode {
633            body: FileBody::Fifo {
634                content: VecDeque::new(),
635                readers: 1,
636                writers: 1,
637            },
638            permissions: Mode::default(),
639        }));
640        let reader = OpenFileDescription {
641            file: Rc::clone(&file),
642            offset: 0,
643            is_readable: true,
644            is_writable: false,
645            is_appending: false,
646        };
647        let writer = OpenFileDescription {
648            file: Rc::clone(&file),
649            offset: 0,
650            is_readable: false,
651            is_writable: true,
652            is_appending: false,
653        };
654
655        let reader = FdBody {
656            open_file_description: Rc::new(RefCell::new(reader)),
657            flags: EnumSet::empty(),
658        };
659        let writer = FdBody {
660            open_file_description: Rc::new(RefCell::new(writer)),
661            flags: EnumSet::empty(),
662        };
663
664        let reader = process.open_fd(reader).unwrap();
665        let writer = process.open_fd(writer).unwrap();
666        (process, reader, writer)
667    }
668
669    #[test]
670    fn process_set_state_wakes_on_resumed() {
671        let mut process = Process::with_parent_and_group(Pid(1), Pid(2));
672        process.state = ProcessState::stopped(signal::SIGTSTP);
673        let process = Rc::new(RefCell::new(process));
674        let process2 = Rc::clone(&process);
675        let waker = Rc::new(Cell::new(None));
676        let task = poll_fn(move |cx| {
677            let mut process = process2.borrow_mut();
678            if process.state() == ProcessState::Running {
679                return Poll::Ready(());
680            }
681            waker.set(Some(cx.waker().clone()));
682            process.wake_on_resumption(Rc::downgrade(&waker));
683            Poll::Pending
684        });
685
686        let mut executor = futures_executor::LocalPool::new();
687        let mut handle = executor.spawner().spawn_local_with_handle(task).unwrap();
688        executor.run_until_stalled();
689        assert_eq!((&mut handle).now_or_never(), None);
690
691        _ = process.borrow_mut().set_state(ProcessState::Running);
692        assert!(executor.try_run_one());
693        assert_eq!(handle.now_or_never(), Some(()));
694    }
695
696    #[test]
697    fn process_set_state_closes_all_fds_on_exit() {
698        let (mut process, _reader, _writer) = process_with_pipe();
699        assert!(process.set_state(ProcessState::exited(3)));
700        assert!(process.fds().is_empty(), "{:?}", process.fds());
701    }
702
703    #[test]
704    fn process_set_state_closes_all_fds_on_signaled() {
705        let (mut process, _reader, _writer) = process_with_pipe();
706        assert!(
707            process.set_state(ProcessState::Halted(ProcessResult::Signaled {
708                signal: signal::SIGINT,
709                core_dump: false
710            }))
711        );
712        assert!(process.fds().is_empty(), "{:?}", process.fds());
713    }
714
715    #[test]
716    fn process_default_signal_blocking_mask() {
717        let process = Process::with_parent_and_group(Pid(10), Pid(11));
718        let initial_set = process.blocked_signals();
719        assert!(initial_set.is_empty(), "{initial_set:?}");
720    }
721
722    #[test]
723    fn process_sigmask_setmask() {
724        let mut process = Process::with_parent_and_group(Pid(10), Pid(11));
725        let result = process.block_signals(SigmaskOp::Set, &[signal::SIGINT, signal::SIGCHLD]);
726        assert_eq!(result, SignalResult::default());
727
728        let result_set = process.blocked_signals();
729        assert!(result_set.contains(&signal::SIGINT));
730        assert!(result_set.contains(&signal::SIGCHLD));
731        assert_eq!(result_set.len(), 2);
732
733        let result = process.block_signals(SigmaskOp::Set, &[signal::SIGINT, signal::SIGQUIT]);
734        assert_eq!(result, SignalResult::default());
735
736        let result_set = process.blocked_signals();
737        assert!(result_set.contains(&signal::SIGINT));
738        assert!(result_set.contains(&signal::SIGQUIT));
739        assert_eq!(result_set.len(), 2);
740    }
741
742    #[test]
743    fn process_sigmask_block() {
744        let mut process = Process::with_parent_and_group(Pid(10), Pid(11));
745        let result = process.block_signals(SigmaskOp::Add, &[signal::SIGINT, signal::SIGCHLD]);
746        assert_eq!(result, SignalResult::default());
747
748        let result_set = process.blocked_signals();
749        assert!(result_set.contains(&signal::SIGINT));
750        assert!(result_set.contains(&signal::SIGCHLD));
751        assert_eq!(result_set.len(), 2);
752
753        let result = process.block_signals(SigmaskOp::Add, &[signal::SIGINT, signal::SIGQUIT]);
754        assert_eq!(result, SignalResult::default());
755
756        let result_set = process.blocked_signals();
757        assert!(result_set.contains(&signal::SIGINT));
758        assert!(result_set.contains(&signal::SIGQUIT));
759        assert!(result_set.contains(&signal::SIGCHLD));
760        assert_eq!(result_set.len(), 3);
761    }
762
763    #[test]
764    fn process_sigmask_unblock() {
765        let mut process = Process::with_parent_and_group(Pid(10), Pid(11));
766        let result = process.block_signals(SigmaskOp::Add, &[signal::SIGINT, signal::SIGCHLD]);
767        assert_eq!(result, SignalResult::default());
768
769        let result = process.block_signals(SigmaskOp::Remove, &[signal::SIGINT, signal::SIGQUIT]);
770        assert_eq!(result, SignalResult::default());
771
772        let result_set = process.blocked_signals();
773        assert!(result_set.contains(&signal::SIGCHLD));
774        assert_eq!(result_set.len(), 1);
775    }
776
777    #[test]
778    fn process_set_disposition() {
779        let mut process = Process::with_parent_and_group(Pid(100), Pid(11));
780        let old_disposition = process.set_disposition(signal::SIGINT, Disposition::Ignore);
781        assert_eq!(old_disposition, Disposition::Default);
782        let old_disposition = process.set_disposition(signal::SIGTERM, Disposition::Catch);
783        assert_eq!(old_disposition, Disposition::Default);
784
785        let old_disposition = process.set_disposition(signal::SIGINT, Disposition::Default);
786        assert_eq!(old_disposition, Disposition::Ignore);
787        let old_disposition = process.set_disposition(signal::SIGTERM, Disposition::Ignore);
788        assert_eq!(old_disposition, Disposition::Catch);
789
790        let disposition = process.disposition(signal::SIGINT);
791        assert_eq!(disposition, Disposition::Default);
792        let disposition = process.disposition(signal::SIGTERM);
793        assert_eq!(disposition, Disposition::Ignore);
794        let disposition = process.disposition(signal::SIGQUIT);
795        assert_eq!(disposition, Disposition::Default);
796    }
797
798    #[test]
799    fn process_raise_signal_default_nop() {
800        let mut process = Process::with_parent_and_group(Pid(42), Pid(11));
801        let result = process.raise_signal(signal::SIGCHLD);
802        assert_eq!(
803            result,
804            SignalResult {
805                delivered: true,
806                caught: false,
807                process_state_changed: false,
808            }
809        );
810        assert_eq!(process.state(), ProcessState::Running);
811    }
812
813    #[test]
814    fn process_raise_signal_default_terminating() {
815        let mut process = Process::with_parent_and_group(Pid(42), Pid(11));
816        let result = process.raise_signal(signal::SIGTERM);
817        assert_eq!(
818            result,
819            SignalResult {
820                delivered: true,
821                caught: false,
822                process_state_changed: true,
823            }
824        );
825        assert_eq!(
826            process.state(),
827            ProcessState::Halted(ProcessResult::Signaled {
828                signal: signal::SIGTERM,
829                core_dump: false
830            })
831        );
832        assert_eq!(process.caught_signals, []);
833    }
834
835    #[test]
836    fn process_raise_signal_default_aborting() {
837        let mut process = Process::with_parent_and_group(Pid(42), Pid(11));
838        let result = process.raise_signal(signal::SIGABRT);
839        assert_eq!(
840            result,
841            SignalResult {
842                delivered: true,
843                caught: false,
844                process_state_changed: true,
845            }
846        );
847        assert_eq!(
848            process.state(),
849            ProcessState::Halted(ProcessResult::Signaled {
850                signal: signal::SIGABRT,
851                core_dump: true
852            })
853        );
854        assert_eq!(process.caught_signals, []);
855        // TODO Check if core dump file has been created
856    }
857
858    #[test]
859    fn process_raise_signal_default_stopping() {
860        let mut process = Process::with_parent_and_group(Pid(42), Pid(11));
861        let result = process.raise_signal(signal::SIGTSTP);
862        assert_eq!(
863            result,
864            SignalResult {
865                delivered: true,
866                caught: false,
867                process_state_changed: true,
868            }
869        );
870        assert_eq!(process.state(), ProcessState::stopped(signal::SIGTSTP));
871        assert_eq!(process.caught_signals, []);
872    }
873
874    #[test]
875    fn process_raise_signal_default_continuing() {
876        let mut process = Process::with_parent_and_group(Pid(42), Pid(11));
877        let _ = process.set_state(ProcessState::stopped(signal::SIGTTOU));
878        let result = process.raise_signal(signal::SIGCONT);
879        assert_eq!(
880            result,
881            SignalResult {
882                delivered: true,
883                caught: false,
884                process_state_changed: true,
885            }
886        );
887        assert_eq!(process.state(), ProcessState::Running);
888        assert_eq!(process.caught_signals, []);
889    }
890
891    #[test]
892    fn process_raise_signal_ignored() {
893        let mut process = Process::with_parent_and_group(Pid(42), Pid(11));
894        process.set_disposition(signal::SIGCHLD, Disposition::Ignore);
895        let result = process.raise_signal(signal::SIGCHLD);
896        assert_eq!(
897            result,
898            SignalResult {
899                delivered: true,
900                caught: false,
901                process_state_changed: false,
902            }
903        );
904        assert_eq!(process.state(), ProcessState::Running);
905        assert_eq!(process.caught_signals, []);
906    }
907
908    #[test]
909    fn process_raise_signal_ignored_and_blocked_sigcont() {
910        let mut process = Process::with_parent_and_group(Pid(42), Pid(11));
911        let _ = process.set_state(ProcessState::stopped(signal::SIGTTOU));
912        let _ = process.set_disposition(signal::SIGCONT, Disposition::Ignore);
913        let _ = process.block_signals(SigmaskOp::Add, &[signal::SIGCONT]);
914        let result = process.raise_signal(signal::SIGCONT);
915        assert_eq!(
916            result,
917            SignalResult {
918                delivered: false,
919                caught: false,
920                process_state_changed: true,
921            }
922        );
923        assert_eq!(process.state(), ProcessState::Running);
924        assert_eq!(process.caught_signals, []);
925        assert!(process.pending_signals.contains(&signal::SIGCONT));
926    }
927
928    #[test]
929    fn process_raise_signal_caught() {
930        let mut process = Process::with_parent_and_group(Pid(42), Pid(11));
931        process.set_disposition(signal::SIGCHLD, Disposition::Catch);
932        let result = process.raise_signal(signal::SIGCHLD);
933        assert_eq!(
934            result,
935            SignalResult {
936                delivered: true,
937                caught: true,
938                process_state_changed: false,
939            }
940        );
941        assert_eq!(process.state(), ProcessState::Running);
942        assert_eq!(process.caught_signals, [signal::SIGCHLD]);
943    }
944
945    #[test]
946    fn process_raise_signal_blocked() {
947        let mut process = Process::with_parent_and_group(Pid(42), Pid(11));
948        process.set_disposition(signal::SIGCHLD, Disposition::Catch);
949        let result = process.block_signals(SigmaskOp::Add, &[signal::SIGCHLD]);
950        assert_eq!(
951            result,
952            SignalResult {
953                delivered: false,
954                caught: false,
955                process_state_changed: false,
956            }
957        );
958
959        let result = process.raise_signal(signal::SIGCHLD);
960        assert_eq!(
961            result,
962            SignalResult {
963                delivered: false,
964                caught: false,
965                process_state_changed: false,
966            }
967        );
968        assert_eq!(process.state(), ProcessState::Running);
969        assert_eq!(process.caught_signals, []);
970
971        let result = process.block_signals(SigmaskOp::Set, &[]);
972        assert_eq!(
973            result,
974            SignalResult {
975                delivered: true,
976                caught: true,
977                process_state_changed: false,
978            }
979        );
980        assert_eq!(process.state(), ProcessState::Running);
981        assert_eq!(process.caught_signals, [signal::SIGCHLD]);
982    }
983}