yash_env/
job.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//! Type definitions for job management.
18//!
19//! A [`JobList`] manages the state of jobs executed by the shell.
20//! Each [`Job`] in the job list remembers the latest state of the child process
21//! performing the job's task.
22//!
23//! The job list stores jobs in an internal array. The index of a job in the
24//! array never changes once the [job is added](JobList::add) to the job list.
25//! The index of the other jobs does not change when you [remove a
26//! job](JobList::remove). Note that the job list may reuse the index of a
27//! removed job for another job added later.
28//!
29//! When the [wait system call](crate::system::Wait::wait) returns a new state of a
30//! child process, the caller should pass it to [`JobList::update_status`],
31//! which modifies the state of the corresponding job. The `state_changed` flag
32//! of the job is set when the job is updated and should be
33//! [reset when reported](JobRefMut::state_reported).
34//!
35//! The job list remembers the selection of two special jobs called the "current
36//! job" and "previous job." The previous job is chosen automatically, so there
37//! is no function to modify it. You can change the current job by
38//! [`JobList::set_current_job`].
39//!
40//! The [`JobList::set_last_async_pid`] function remembers the process ID of the
41//! last executed asynchronous command, which will be the value of the `$!`
42//! special parameter.
43
44use crate::Env;
45use crate::io::Fd;
46use crate::semantics::{Divert, ExitStatus};
47use crate::signal;
48use crate::system::{Disposition, Errno, Sigaction, Sigmask, SigmaskOp, Signals, TcSetPgrp};
49use slab::Slab;
50use std::collections::HashMap;
51use std::iter::FusedIterator;
52use std::ops::ControlFlow::{Break, Continue};
53use std::ops::Deref;
54use thiserror::Error;
55
56#[cfg(unix)]
57type RawPidDef = libc::pid_t;
58#[cfg(not(unix))]
59type RawPidDef = i32;
60
61/// Raw process ID type
62///
63/// This is a type alias for the raw process ID type `pid_t` declared in the
64/// [`libc`] crate. The exact representation of this type is platform-dependent
65/// while POSIX requires the type to be a signed integer. On non-Unix platforms,
66/// this type is hard-coded to `i32`.
67///
68/// Process IDs are usually wrapped in the [`Pid`] type for better type safety,
69/// so this type is not used directly in most cases.
70pub type RawPid = RawPidDef;
71
72/// Process ID
73///
74/// A process ID is an integer that identifies a process in the system. This
75/// type implements the new type pattern around the raw process ID type
76/// [`RawPid`].  The advantage of using this type is that it is more type-safe
77/// than using the raw integer value directly.
78///
79/// Although genuine process IDs are always positive integers, this type allows
80/// zero or negative values for the purpose of specifying a group of processes
81/// when used as a parameter to the [`kill`] and [`wait`] system calls. The
82/// [`setpgid`] system call also uses process ID zero to specify the process
83/// ID of the calling process.
84///
85/// This type may also be used to represent process group IDs, session IDs, etc.
86///
87/// [`kill`]: crate::system::SendSignal::kill
88/// [`wait`]: crate::system::Wait::wait
89/// [`setpgid`]: crate::system::SetPgid::setpgid
90#[repr(transparent)]
91#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
92pub struct Pid(pub RawPid);
93
94impl std::fmt::Display for Pid {
95    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96        self.0.fmt(f)
97    }
98}
99
100impl std::ops::Neg for Pid {
101    type Output = Self;
102    fn neg(self) -> Self {
103        Self(-self.0)
104    }
105}
106
107impl Pid {
108    /// Sentinel value for the [`kill`] and [`wait`] system calls specifying all
109    /// processes in the process group of the calling process.
110    ///
111    /// [`kill`]: crate::system::SendSignal::kill
112    /// [`wait`]: crate::system::Wait::wait
113    pub const MY_PROCESS_GROUP: Self = Pid(0);
114
115    /// Sentinel value for the [`kill`] and [`wait`] system calls specifying all
116    /// possible processes.
117    ///
118    /// [`kill`]: crate::system::SendSignal::kill
119    /// [`wait`]: crate::system::Wait::wait
120    pub const ALL: Self = Pid(-1);
121}
122
123/// Execution state of a process from which the exit status can be computed
124///
125/// This type is used to represent the result of a process execution. It is
126/// similar to the `WaitStatus` type defined in the `nix` crate, but it is
127/// simplified to represent only the states that are relevant to the shell.
128///
129/// This type only contains the states the process's exit status can be computed
130/// from. See also [`ProcessState`], which is a more general type that includes
131/// the states that are not directly related to the exit status.
132#[derive(Clone, Copy, Debug, Eq, PartialEq)]
133pub enum ProcessResult {
134    /// The process has been stopped by a signal.
135    Stopped(signal::Number),
136    /// The process has exited.
137    Exited(ExitStatus),
138    /// The process has been terminated by a signal.
139    Signaled {
140        signal: signal::Number,
141        core_dump: bool,
142    },
143}
144
145impl ProcessResult {
146    /// Creates a new `ProcessResult` instance representing an exited process.
147    #[inline]
148    #[must_use]
149    pub fn exited<S: Into<ExitStatus>>(exit_status: S) -> Self {
150        Self::Exited(exit_status.into())
151    }
152
153    /// Whether the process is stopped
154    #[must_use]
155    pub fn is_stopped(&self) -> bool {
156        matches!(self, ProcessResult::Stopped(_))
157    }
158}
159
160/// Converts `ProcessResult` to `ExitStatus`.
161impl From<ProcessResult> for ExitStatus {
162    fn from(result: ProcessResult) -> Self {
163        match result {
164            ProcessResult::Exited(exit_status) => exit_status,
165            ProcessResult::Stopped(signal) | ProcessResult::Signaled { signal, .. } => {
166                ExitStatus::from(signal)
167            }
168        }
169    }
170}
171
172/// Execution state of a process, either running or halted
173///
174/// This type is used to represent the current state of a process. It is similar
175/// to the `WaitStatus` type defined in the `nix` crate, but it is simplified to
176/// represent only the states that are relevant to the shell.
177///
178/// This type can represent all possible states of a process, including running,
179/// stopped, exited, and signaled states. When the process is not running, the
180/// state is represented by a [`ProcessResult`].
181#[derive(Clone, Copy, Debug, Eq, PartialEq)]
182pub enum ProcessState {
183    /// The process is running.
184    Running,
185    /// The process has exited, stopped, or been terminated by a signal.
186    Halted(ProcessResult),
187}
188
189impl ProcessState {
190    /// Creates a new `ProcessState` instance representing a stopped process.
191    #[inline]
192    #[must_use]
193    pub fn stopped(signal: signal::Number) -> Self {
194        Self::Halted(ProcessResult::Stopped(signal))
195    }
196
197    /// Creates a new `ProcessState` instance representing an exited process.
198    #[inline]
199    #[must_use]
200    pub fn exited<S: Into<ExitStatus>>(exit_status: S) -> Self {
201        Self::Halted(ProcessResult::exited(exit_status))
202    }
203
204    /// Whether the process is not yet terminated
205    #[must_use]
206    pub fn is_alive(&self) -> bool {
207        match self {
208            ProcessState::Running => true,
209            ProcessState::Halted(result) => result.is_stopped(),
210        }
211    }
212
213    /// Whether the process is stopped
214    #[must_use]
215    pub fn is_stopped(&self) -> bool {
216        matches!(self, Self::Halted(result) if result.is_stopped())
217    }
218}
219
220impl From<ProcessResult> for ProcessState {
221    #[inline]
222    fn from(result: ProcessResult) -> Self {
223        Self::Halted(result)
224    }
225}
226
227/// Error value indicating that the process is running.
228///
229/// This error value may be returned by [`TryFrom<ProcessState>::try_from`].
230#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
231pub struct RunningProcess;
232
233/// Converts `ProcessState` to `ExitStatus`.
234///
235/// For the `Running` state, the conversion fails with [`RunningProcess`].
236impl TryFrom<ProcessState> for ExitStatus {
237    type Error = RunningProcess;
238    fn try_from(state: ProcessState) -> Result<Self, RunningProcess> {
239        match state {
240            ProcessState::Halted(result) => Ok(result.into()),
241            ProcessState::Running => Err(RunningProcess),
242        }
243    }
244}
245
246/// Set of one or more processes executing a pipeline
247///
248/// In the current implementation, a job contains the process ID of one child
249/// process of the shell. Though there may be more processes involved in the
250/// execution of the pipeline, the shell takes care of only one process of the
251/// job.
252#[derive(Clone, Debug, Eq, PartialEq)]
253#[non_exhaustive]
254pub struct Job {
255    /// Process ID
256    ///
257    /// If the job is job-controlled, this is also the process group ID.
258    pub pid: Pid,
259
260    /// Whether the job is job-controlled.
261    ///
262    /// If the job is job-controlled, the job processes run in their own process
263    /// group.
264    pub job_controlled: bool,
265
266    /// Current state of the process
267    pub state: ProcessState,
268
269    /// State of the process expected in the next update
270    ///
271    /// See [`JobRefMut::expect`] and [`JobList::update_status`] for details.
272    pub expected_state: Option<ProcessState>,
273
274    /// Indicator of state change
275    ///
276    /// This flag is true if the `state` has been changed since the state was
277    /// last reported to the user.
278    pub state_changed: bool,
279
280    /// Whether this job is a true child of the current shell
281    ///
282    /// When a subshell is created, the jobs inherited from the parent shell are
283    /// marked as not owned by the current shell. The shell cannot wait for
284    /// these jobs to finish.
285    pub is_owned: bool,
286
287    /// String representation of this process
288    pub name: String,
289}
290
291impl Job {
292    /// Creates a new job instance.
293    ///
294    /// This function requires a process ID to initialize the new job. The other
295    /// members of the job are defaulted.
296    pub fn new(pid: Pid) -> Self {
297        Job {
298            pid,
299            job_controlled: false,
300            state: ProcessState::Running,
301            expected_state: None,
302            state_changed: true,
303            is_owned: true,
304            name: String::new(),
305        }
306    }
307
308    /// Whether the job is suspended
309    #[must_use]
310    fn is_suspended(&self) -> bool {
311        self.state.is_stopped()
312    }
313}
314
315/// Partially mutable reference to [`Job`].
316///
317/// This struct is a specialized reference type for `Job`. It provides limited
318/// mutability over the `Job` instance through its methods. It also allows
319/// unlimited immutable access through the `Deref` implementation.
320#[derive(Debug, Eq, PartialEq)]
321pub struct JobRefMut<'a>(&'a mut Job);
322
323impl JobRefMut<'_> {
324    /// Sets the `expected_state` of the job.
325    ///
326    /// This method remembers the argument as the expected state of the job.
327    /// If the job is [updated] with the same state, the `state_changed` flag
328    /// is not set then.
329    ///
330    /// This method may be used to suppress a change report of a job state,
331    /// especially when the state is reported before it is actually changed.
332    ///
333    /// [updated]: JobList::update_status
334    pub fn expect<S>(&mut self, state: S)
335    where
336        S: Into<Option<ProcessState>>,
337    {
338        self.0.expected_state = state.into();
339    }
340
341    /// Clears the `state_changed` flag of the job.
342    ///
343    /// Normally, this method should be called when the shell printed a job
344    /// status report.
345    pub fn state_reported(&mut self) {
346        self.0.state_changed = false
347    }
348}
349
350impl Deref for JobRefMut<'_> {
351    type Target = Job;
352    fn deref(&self) -> &Job {
353        self.0
354    }
355}
356
357/// Indexed iterator of jobs.
358///
359/// Call [`JobList::iter`] to get an instance of `Iter`.
360#[derive(Clone, Debug)]
361pub struct Iter<'a>(slab::Iter<'a, Job>);
362
363impl<'a> Iterator for Iter<'a> {
364    type Item = (usize, &'a Job);
365
366    #[inline(always)]
367    fn next(&mut self) -> Option<(usize, &'a Job)> {
368        self.0.next()
369    }
370
371    #[inline(always)]
372    fn size_hint(&self) -> (usize, Option<usize>) {
373        self.0.size_hint()
374    }
375}
376
377impl<'a> DoubleEndedIterator for Iter<'a> {
378    #[inline(always)]
379    fn next_back(&mut self) -> Option<(usize, &'a Job)> {
380        self.0.next_back()
381    }
382}
383
384impl ExactSizeIterator for Iter<'_> {
385    #[inline(always)]
386    fn len(&self) -> usize {
387        self.0.len()
388    }
389}
390
391impl FusedIterator for Iter<'_> {}
392
393/// Indexed iterator of partially mutable jobs.
394///
395/// Call [`JobList::iter_mut`] to get an instance of `IterMut`.
396#[derive(Debug)]
397pub struct IterMut<'a>(slab::IterMut<'a, Job>);
398
399impl<'a> Iterator for IterMut<'a> {
400    type Item = (usize, JobRefMut<'a>);
401
402    #[inline]
403    fn next(&mut self) -> Option<(usize, JobRefMut<'a>)> {
404        self.0.next().map(|(index, job)| (index, JobRefMut(job)))
405    }
406
407    #[inline(always)]
408    fn size_hint(&self) -> (usize, Option<usize>) {
409        self.0.size_hint()
410    }
411}
412
413impl<'a> DoubleEndedIterator for IterMut<'a> {
414    fn next_back(&mut self) -> Option<(usize, JobRefMut<'a>)> {
415        self.0
416            .next_back()
417            .map(|(index, job)| (index, JobRefMut(job)))
418    }
419}
420
421impl ExactSizeIterator for IterMut<'_> {
422    #[inline(always)]
423    fn len(&self) -> usize {
424        self.0.len()
425    }
426}
427
428impl FusedIterator for IterMut<'_> {}
429
430/// Collection of jobs.
431///
432/// See the [module documentation](self) for details.
433#[derive(Clone, Debug)]
434pub struct JobList {
435    /// Jobs managed by the shell
436    jobs: Slab<Job>,
437
438    /// Map from process IDs to indices of `jobs`
439    ///
440    /// This is a shortcut to quickly find jobs by process ID.
441    pids_to_indices: HashMap<Pid, usize>,
442
443    /// Index of the current job. (Only valid when the list is non-empty)
444    current_job_index: usize,
445
446    /// Index of the previous job. (Only valid when the list is non-empty)
447    previous_job_index: usize,
448
449    /// Process ID of the most recently executed asynchronous command.
450    last_async_pid: Pid,
451}
452
453impl Default for JobList {
454    fn default() -> Self {
455        JobList {
456            jobs: Slab::new(),
457            pids_to_indices: HashMap::new(),
458            current_job_index: usize::default(),
459            previous_job_index: usize::default(),
460            last_async_pid: Pid(0),
461        }
462    }
463}
464
465impl JobList {
466    /// Creates an empty job list.
467    #[inline]
468    #[must_use]
469    pub fn new() -> Self {
470        Self::default()
471    }
472
473    /// Returns the job at the specified index.
474    ///
475    /// The result is `None` if there is no job for the index.
476    #[inline]
477    pub fn get(&self, index: usize) -> Option<&Job> {
478        self.jobs.get(index)
479    }
480
481    /// Returns a partially mutable reference to the job at the specified index.
482    ///
483    /// The result is `None` if there is no job for the index.
484    #[inline]
485    pub fn get_mut(&mut self, index: usize) -> Option<JobRefMut<'_>> {
486        self.jobs.get_mut(index).map(JobRefMut)
487    }
488
489    /// Returns the number of jobs in this job list.
490    #[inline]
491    pub fn len(&self) -> usize {
492        self.jobs.len()
493    }
494
495    /// Returns true if this job list contains no jobs.
496    #[inline]
497    pub fn is_empty(&self) -> bool {
498        self.len() == 0
499    }
500
501    /// Returns an indexed iterator of jobs.
502    ///
503    /// The item type of the returned iterator is `(usize, &Job)`.
504    /// Jobs are iterated in the order of indices.
505    #[inline]
506    pub fn iter(&self) -> Iter<'_> {
507        Iter(self.jobs.iter())
508    }
509
510    /// Returns an indexed iterator of partially mutable jobs.
511    ///
512    /// The item type of the returned iterator is `(usize, JobRefMut)`.
513    /// Note that the iterator does not yield raw mutable references to jobs.
514    /// [`JobRefMut`] allows mutating only part of jobs.
515    ///
516    /// Jobs are iterated in the order of indices.
517    #[inline]
518    pub fn iter_mut(&mut self) -> IterMut<'_> {
519        IterMut(self.jobs.iter_mut())
520    }
521
522    /// Finds a job by the process ID.
523    ///
524    /// This function returns the index of the job whose process ID is `pid`.
525    /// The result is `None` if no such job is found.
526    ///
527    /// A `JobList` maintains an internal hash map to quickly find jobs by
528    /// process ID.
529    pub fn find_by_pid(&self, pid: Pid) -> Option<usize> {
530        self.pids_to_indices.get(&pid).copied()
531    }
532}
533
534impl<'a> IntoIterator for &'a JobList {
535    type Item = (usize, &'a Job);
536    type IntoIter = Iter<'a>;
537    #[inline(always)]
538    fn into_iter(self) -> Iter<'a> {
539        self.iter()
540    }
541}
542
543impl<'a> IntoIterator for &'a mut JobList {
544    type Item = (usize, JobRefMut<'a>);
545    type IntoIter = IterMut<'a>;
546    #[inline(always)]
547    fn into_iter(self) -> IterMut<'a> {
548        self.iter_mut()
549    }
550}
551
552/// Supports indexing operation on `JobList`.
553impl std::ops::Index<usize> for JobList {
554    type Output = Job;
555
556    /// Returns a reference to the specified job.
557    ///
558    /// This function will panic if the job does not exist.
559    fn index(&self, index: usize) -> &Job {
560        &self.jobs[index]
561    }
562}
563
564/// Iterator that conditionally removes jobs from a job list.
565///
566/// Call [`JobList::extract_if`] to get an instance of `ExtractIf`.
567#[derive(Debug)]
568pub struct ExtractIf<'a, F>
569where
570    F: FnMut(usize, JobRefMut) -> bool,
571{
572    list: &'a mut JobList,
573    should_remove: F,
574    next_index: usize,
575    len: usize,
576}
577
578impl<F> Iterator for ExtractIf<'_, F>
579where
580    F: FnMut(usize, JobRefMut) -> bool,
581{
582    type Item = (usize, Job);
583
584    fn next(&mut self) -> Option<(usize, Job)> {
585        while self.len > 0 {
586            let index = self.next_index;
587            self.next_index += 1;
588            if let Some(job) = self.list.get_mut(index) {
589                self.len -= 1;
590                if (self.should_remove)(index, job) {
591                    let job = self.list.remove(index).unwrap();
592                    return Some((index, job));
593                }
594            }
595        }
596        None
597    }
598
599    fn size_hint(&self) -> (usize, Option<usize>) {
600        (0, Some(self.len))
601    }
602}
603
604impl<F> FusedIterator for ExtractIf<'_, F> where F: FnMut(usize, JobRefMut) -> bool {}
605
606impl JobList {
607    /// Adds a job to this job list.
608    ///
609    /// This function returns a unique index assigned to the job.
610    ///
611    /// If there already is a job that has the same process ID as that of the
612    /// new job, the existing job is silently removed.
613    ///
614    /// If the new job is suspended and the [current job](Self::current_job) is
615    /// not, the new job becomes the current job. If the new job and the current
616    /// job are suspended but the [previous job](Self::previous_job) is not, the
617    /// new job becomes the previous job.
618    pub fn add(&mut self, job: Job) -> usize {
619        let new_job_is_suspended = job.is_suspended();
620        let ex_current_job_is_suspended =
621            self.current_job().map(|index| self[index].is_suspended());
622        let ex_previous_job_is_suspended =
623            self.previous_job().map(|index| self[index].is_suspended());
624
625        // Add the job to `self.jobs` and `self.pids_to_indices`.
626        use std::collections::hash_map::Entry::*;
627        let index = match self.pids_to_indices.entry(job.pid) {
628            Vacant(entry) => {
629                let index = self.jobs.insert(job);
630                entry.insert(index);
631                index
632            }
633            Occupied(entry) => {
634                let index = *entry.get();
635                self.jobs[index] = job;
636                index
637            }
638        };
639        debug_assert_eq!(self.jobs.len(), self.pids_to_indices.len());
640
641        // Reselect the current and previous job.
642        match ex_current_job_is_suspended {
643            None => self.current_job_index = index,
644            Some(false) if new_job_is_suspended => self.set_current_job(index).unwrap(),
645            Some(_) => match ex_previous_job_is_suspended {
646                None => self.previous_job_index = index,
647                Some(false) if new_job_is_suspended => self.previous_job_index = index,
648                Some(_) => (),
649            },
650        }
651
652        index
653    }
654
655    /// Removes a job from this job list.
656    ///
657    /// This function returns the job removed from the job list.
658    /// The result is `None` if there is no job for the index.
659    ///
660    /// If the removed job is the [current job](Self::current_job), the
661    /// [previous job](Self::previous_job) becomes the current job and another
662    /// job is selected for the new previous job, if any.
663    /// If the removed job is the previous job, another job is selected for the
664    /// new previous job, if any.
665    pub fn remove(&mut self, index: usize) -> Option<Job> {
666        let job = self.jobs.try_remove(index);
667
668        if let Some(job) = &job {
669            // Keep `pids_to_indices` in sync
670            self.pids_to_indices.remove(&job.pid);
671
672            if self.jobs.is_empty() {
673                // Clearing an already empty slab may seem redundant, but this
674                // operation purges the slab's internal cache of unused indices,
675                // so that jobs added later have indices starting from 0.
676                self.jobs.clear();
677            }
678
679            // Reselect the current and previous job
680            let previous_job_becomes_current_job = index == self.current_job_index;
681            if previous_job_becomes_current_job {
682                self.current_job_index = self.previous_job_index;
683            }
684            if previous_job_becomes_current_job || index == self.previous_job_index {
685                self.previous_job_index = self
686                    .any_suspended_job_but_current()
687                    .unwrap_or_else(|| self.any_job_but_current().unwrap_or_default());
688            }
689        }
690        debug_assert_eq!(self.jobs.len(), self.pids_to_indices.len());
691
692        job
693    }
694
695    /// Removes jobs that satisfy the predicate.
696    ///
697    /// This function uses the `should_remove` function to decide whether to
698    /// remove jobs. If it returns true, the job is removed and yielded from the
699    /// iterator. Otherwise, the job remains in the list.
700    ///
701    /// You can reset the `state_changed` flag of a job
702    /// ([`JobRefMut::state_reported`]) regardless of whether you choose to
703    /// remove it or not.
704    ///
705    /// This function is a simplified version of [`JobList::extract_if`] that
706    /// does not return removed jobs.
707    pub fn remove_if<F>(&mut self, should_remove: F)
708    where
709        F: FnMut(usize, JobRefMut) -> bool,
710    {
711        self.extract_if(should_remove).for_each(drop)
712    }
713
714    /// Returns an iterator that conditionally removes jobs.
715    ///
716    /// The iterator uses the `should_remove` function to decide whether to
717    /// remove jobs. If it returns true, the job is removed and yielded from the
718    /// iterator. Otherwise, the job remains in the list.
719    ///
720    /// You can reset the `state_changed` flag of a job
721    /// ([`JobRefMut::state_reported`]) regardless of whether you choose to
722    /// remove it or not.
723    ///
724    /// If the returned iterator is dropped before iterating all jobs, the
725    /// remaining jobs are retained in the list.
726    ///
727    /// If you don't need to take the ownership of removed jobs, consider using
728    /// [`JobList::remove_if`] instead.
729    pub fn extract_if<F>(&mut self, should_remove: F) -> ExtractIf<'_, F>
730    where
731        F: FnMut(usize, JobRefMut) -> bool,
732    {
733        let len = self.len();
734        ExtractIf {
735            list: self,
736            should_remove,
737            next_index: 0,
738            len,
739        }
740    }
741}
742
743impl JobList {
744    /// Updates the state of a job.
745    ///
746    /// The result of a [`wait`](crate::system::Wait::wait) call should be passed to
747    /// this function. It looks up the job for the given process ID, updates the
748    /// state of the job to the given `state`, and sets the `state_changed` flag
749    /// in the job. As an exception, if `state` is equal to the `expected_state`
750    /// of the job, the `state_changed` flag is not set. The `expected_state` is
751    /// cleared in any case. (See also [`JobRefMut::expect`] for the usage of
752    /// `expected_state`.)
753    ///
754    /// Returns the index of the job updated. If there is no job for the given
755    /// process ID, the result is `None`.
756    ///
757    /// When a job is suspended (i.e., `state` is `Stopped`), the job becomes
758    /// the [current job](Self::current_job) and the old current job becomes the
759    /// [previous job](Self::previous_job). When a suspended job gets a state
760    /// update:
761    ///
762    /// - If the updated job is the current job and the previous job is
763    ///   suspended, the previous job becomes the current job and the new
764    ///   previous job is chosen from other suspended jobs. If there is no
765    ///   suspended jobs, the new previous jobs is the old current job.
766    /// - If the updated job is the previous job and there is a suspended job
767    ///   other than the current job, it becomes the previous job.
768    pub fn update_status(&mut self, pid: Pid, state: ProcessState) -> Option<usize> {
769        let index = self.find_by_pid(pid)?;
770
771        // Update the job state.
772        let job = &mut self.jobs[index];
773        let was_suspended = job.is_suspended();
774        job.state = state;
775        job.state_changed |= job.expected_state != Some(state);
776        job.expected_state = None;
777
778        // Reselect the current and previous job.
779        if !was_suspended && job.is_suspended() {
780            if index != self.current_job_index {
781                self.previous_job_index = std::mem::replace(&mut self.current_job_index, index);
782            }
783        } else if was_suspended && !job.is_suspended() {
784            if let Some(prev_index) = self.previous_job() {
785                let previous_job_becomes_current_job =
786                    index == self.current_job_index && self[prev_index].is_suspended();
787                if previous_job_becomes_current_job {
788                    self.current_job_index = prev_index;
789                }
790                if previous_job_becomes_current_job || index == prev_index {
791                    self.previous_job_index = self.any_suspended_job_but_current().unwrap_or(index);
792                }
793            }
794        }
795
796        Some(index)
797    }
798
799    /// Disowns all jobs.
800    ///
801    /// This function sets the `is_owned` flag of all jobs to `false`.
802    pub fn disown_all(&mut self) {
803        for (_, job) in &mut self.jobs {
804            job.is_owned = false;
805        }
806    }
807}
808
809/// Error type for [`JobList::set_current_job`].
810#[derive(Clone, Copy, Debug, Eq, Error, Hash, PartialEq)]
811pub enum SetCurrentJobError {
812    /// The specified index does not refer to any job.
813    #[error("no such job")]
814    NoSuchJob,
815
816    /// The specified job is not a suspended job and there is another suspended
817    /// job.
818    #[error("the current job must be selected from suspended jobs")]
819    NotSuspended,
820}
821
822impl JobList {
823    /// Selects the current job.
824    ///
825    /// This function changes the current job to the job specified by the index
826    /// and the previous job to the old current job.
827    ///
828    /// If there is one or more suspended jobs, the current job must be selected
829    /// from them. If the index does not refer to a suspended job, the
830    /// `NotSuspended` error is returned.
831    ///
832    /// If the index does not refer to any job, the `NoSuchJob` error is
833    /// returned.
834    pub fn set_current_job(&mut self, index: usize) -> Result<(), SetCurrentJobError> {
835        let job = self.get(index).ok_or(SetCurrentJobError::NoSuchJob)?;
836        if !job.is_suspended() && self.iter().any(|(_, job)| job.is_suspended()) {
837            return Err(SetCurrentJobError::NotSuspended);
838        }
839
840        if index != self.current_job_index {
841            self.previous_job_index = std::mem::replace(&mut self.current_job_index, index);
842        }
843        Ok(())
844    }
845
846    /// Returns the index of the current job.
847    ///
848    /// If the job list contains at least one job, there is a current job. This
849    /// function returns its index. If the job list is empty, the result is
850    /// `None`.
851    ///
852    /// If there is any suspended jobs, the current job must be a suspended job.
853    /// Running or terminated jobs can be the current job if there is no
854    /// suspended job. You can [change the current job](Self::set_current_job)
855    /// as long as the above rules are met.
856    ///
857    /// See also [`previous_job`](Self::previous_job).
858    pub fn current_job(&self) -> Option<usize> {
859        if self.jobs.contains(self.current_job_index) {
860            Some(self.current_job_index)
861        } else {
862            None
863        }
864    }
865
866    /// Returns the index of the previous job.
867    ///
868    /// If the job list contains two or more jobs, there is a previous job. This
869    /// function returns its index. If the job list has zero or one job, the
870    /// result is `None`.
871    ///
872    /// The previous job is never the same job as the [current
873    /// job](Self::current_job).
874    ///
875    /// If there are two or more suspended jobs, the previous job must be a
876    /// suspended job.  Running or terminated jobs can be the previous job if
877    /// there is zero or one suspended job.
878    ///
879    /// You cannot directly select the previous job. When the current job is
880    /// selected, the old current job becomes the previous job.
881    pub fn previous_job(&self) -> Option<usize> {
882        if self.previous_job_index != self.current_job_index
883            && self.jobs.contains(self.previous_job_index)
884        {
885            Some(self.previous_job_index)
886        } else {
887            None
888        }
889    }
890
891    /// Finds a suspended job other than the current job.
892    fn any_suspended_job_but_current(&self) -> Option<usize> {
893        self.iter()
894            .filter(|&(index, job)| index != self.current_job_index && job.is_suspended())
895            .map(|(index, _)| index)
896            .next()
897    }
898
899    /// Finds a job other than the current job.
900    fn any_job_but_current(&self) -> Option<usize> {
901        self.iter()
902            .filter(|&(index, _)| index != self.current_job_index)
903            .map(|(index, _)| index)
904            .next()
905    }
906}
907
908impl JobList {
909    /// Returns the process ID of the most recently executed asynchronous
910    /// command.
911    ///
912    /// This function returns the value that has been set by
913    /// [`set_last_async_pid`](Self::set_last_async_pid), or 0 if no value has
914    /// been set.
915    pub fn last_async_pid(&self) -> Pid {
916        self.last_async_pid
917    }
918
919    /// Sets the process ID of the most recently executed asynchronous command.
920    ///
921    /// This function affects the result of
922    /// [`last_async_pid`](Self::last_async_pid).
923    pub fn set_last_async_pid(&mut self, pid: Pid) {
924        self.last_async_pid = pid;
925    }
926}
927
928/// Switches the foreground process group with SIGTTOU blocked.
929///
930/// This is a convenience function to change the foreground process group
931/// safely. If you call [`TcSetPgrp::tcsetpgrp`] from a background process, the
932/// process is stopped by SIGTTOU by default. To prevent this effect, SIGTTOU
933/// must be blocked or ignored when `tcsetpgrp` is called. This function uses
934/// [`Sigmask::sigmask`] to block SIGTTOU before calling `tcsetpgrp` and also to
935/// restore the original signal mask after `tcsetpgrp`.
936///
937/// Use [`tcsetpgrp_without_block`] if you need to make sure the shell is in the
938/// foreground before changing the foreground job.
939pub async fn tcsetpgrp_with_block<S>(system: &S, fd: Fd, pgid: Pid) -> crate::system::Result<()>
940where
941    S: Signals + Sigmask + TcSetPgrp + ?Sized,
942{
943    let sigttou = system
944        .signal_number_from_name(signal::Name::Ttou)
945        .ok_or(Errno::EINVAL)?;
946    let mut old_mask = Vec::new();
947
948    system.sigmask(Some((SigmaskOp::Add, &[sigttou])), Some(&mut old_mask))?;
949
950    let result = system.tcsetpgrp(fd, pgid).await;
951
952    let result_2 = system.sigmask(Some((SigmaskOp::Set, &old_mask)), None);
953
954    result.and(result_2)
955}
956
957/// Switches the foreground process group with the default SIGTTOU settings.
958///
959/// This is a convenience function to ensure the shell has been in the
960/// foreground and optionally change the foreground process group. This
961/// function calls [`Sigaction::sigaction`] to restore the action for
962/// SIGTTOU to the default disposition (which is to suspend the shell
963/// process), [`Sigmask::sigmask`] to unblock SIGTTOU, and
964/// [`TcSetPgrp::tcsetpgrp`] to modify the foreground job. If the calling
965/// process is not in the foreground, `tcsetpgrp` will suspend the process
966/// with SIGTTOU until another job-controlling process resumes it in the
967/// foreground. After `tcsetpgrp` completes, this function calls `sigmask`
968/// and `sigaction` to restore the original state.
969///
970/// Note that if `pgid` is the process group ID of the current process, this
971/// function does not change the foreground job, but the process is still
972/// subject to suspension if it has not been in the foreground.
973///
974/// Use [`tcsetpgrp_with_block`] to change the job even if the current shell is
975/// not in the foreground.
976pub async fn tcsetpgrp_without_block<S>(system: &S, fd: Fd, pgid: Pid) -> crate::system::Result<()>
977where
978    S: Signals + Sigaction + Sigmask + TcSetPgrp + ?Sized,
979{
980    let sigttou = system
981        .signal_number_from_name(signal::Name::Ttou)
982        .ok_or(Errno::EINVAL)?;
983    match system.sigaction(sigttou, Disposition::Default) {
984        Err(e) => Err(e),
985        Ok(old_handling) => {
986            let mut old_mask = Vec::new();
987            let result =
988                match system.sigmask(Some((SigmaskOp::Remove, &[sigttou])), Some(&mut old_mask)) {
989                    Err(e) => Err(e),
990                    Ok(()) => {
991                        let result = system.tcsetpgrp(fd, pgid).await;
992
993                        let result_2 = system.sigmask(Some((SigmaskOp::Set, &old_mask)), None);
994
995                        result.and(result_2)
996                    }
997                };
998
999            let result_2 = system.sigaction(sigttou, old_handling).map(drop);
1000
1001            result.and(result_2)
1002        }
1003    }
1004}
1005
1006/// Adds a job if the process is suspended.
1007///
1008/// This is a convenience function for handling the result of
1009/// [`Subshell::start_and_wait`](crate::subshell::Subshell::start_and_wait).
1010///
1011/// If the process result indicates that the process is stopped, this function
1012/// adds a job to the job list in the environment. The job is marked as
1013/// job-controlled and its state is derived from the process result. The job
1014/// name is set to the result of the `name` closure. If the current environment
1015/// is interactive, this function returns
1016/// `Break(Divert::Interrupt(Some(exit_status)))` to indicate that the shell
1017/// should be interrupted.
1018///
1019/// If the process is not stopped, this function does not add a job.
1020///
1021/// Returns the exit status of the process that should be assigned to
1022/// `env.exit_status`.
1023pub fn add_job_if_suspended<S, F>(
1024    env: &mut Env<S>,
1025    pid: Pid,
1026    result: ProcessResult,
1027    name: F,
1028) -> crate::semantics::Result<ExitStatus>
1029where
1030    F: FnOnce() -> String,
1031{
1032    let exit_status = result.into();
1033
1034    if result.is_stopped() {
1035        let mut job = Job::new(pid);
1036        job.job_controlled = true;
1037        job.state = result.into();
1038        job.name = name();
1039        env.jobs.add(job);
1040
1041        if env.is_interactive() {
1042            return Break(Divert::Interrupt(Some(exit_status)));
1043        }
1044    }
1045
1046    Continue(exit_status)
1047}
1048
1049pub mod fmt;
1050pub mod id;
1051
1052#[cfg(test)]
1053mod tests {
1054    use super::*;
1055    use crate::option::Option::Interactive;
1056    use crate::option::State::On;
1057    use crate::signal;
1058    use crate::system::r#virtual::{SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU};
1059    use std::num::NonZero;
1060
1061    #[test]
1062    fn job_list_find_by_pid() {
1063        let mut list = JobList::default();
1064        assert_eq!(list.find_by_pid(Pid(10)), None);
1065
1066        let i10 = list.add(Job::new(Pid(10)));
1067        let i20 = list.add(Job::new(Pid(20)));
1068        let i30 = list.add(Job::new(Pid(30)));
1069        assert_eq!(list.find_by_pid(Pid(10)), Some(i10));
1070        assert_eq!(list.find_by_pid(Pid(20)), Some(i20));
1071        assert_eq!(list.find_by_pid(Pid(30)), Some(i30));
1072        assert_eq!(list.find_by_pid(Pid(40)), None);
1073
1074        list.remove(i10);
1075        assert_eq!(list.find_by_pid(Pid(10)), None);
1076    }
1077
1078    #[test]
1079    fn job_list_add_and_remove() {
1080        // This test case depends on how Slab reuses the index of removed items.
1081        let mut list = JobList::default();
1082
1083        assert_eq!(list.add(Job::new(Pid(10))), 0);
1084        assert_eq!(list.add(Job::new(Pid(11))), 1);
1085        assert_eq!(list.add(Job::new(Pid(12))), 2);
1086
1087        assert_eq!(list.remove(0).unwrap().pid, Pid(10));
1088        assert_eq!(list.remove(1).unwrap().pid, Pid(11));
1089
1090        // Indices are reused in the reverse order of removals.
1091        assert_eq!(list.add(Job::new(Pid(13))), 1);
1092        assert_eq!(list.add(Job::new(Pid(14))), 0);
1093
1094        assert_eq!(list.remove(0).unwrap().pid, Pid(14));
1095        assert_eq!(list.remove(1).unwrap().pid, Pid(13));
1096        assert_eq!(list.remove(2).unwrap().pid, Pid(12));
1097
1098        // Once the job list is empty, indices start from 0 again.
1099        assert_eq!(list.add(Job::new(Pid(13))), 0);
1100        assert_eq!(list.add(Job::new(Pid(14))), 1);
1101    }
1102
1103    #[test]
1104    fn job_list_add_same_pid() {
1105        let mut list = JobList::default();
1106
1107        let mut job = Job::new(Pid(10));
1108        job.name = "first job".to_string();
1109        let i_first = list.add(job);
1110
1111        let mut job = Job::new(Pid(10));
1112        job.name = "second job".to_string();
1113        let i_second = list.add(job);
1114
1115        let job = &list[i_second];
1116        assert_eq!(job.pid, Pid(10));
1117        assert_eq!(job.name, "second job");
1118
1119        assert_ne!(
1120            list.get(i_first).map(|job| job.name.as_str()),
1121            Some("first job")
1122        );
1123    }
1124
1125    #[test]
1126    fn job_list_extract_if() {
1127        let mut list = JobList::default();
1128        let i21 = list.add(Job::new(Pid(21)));
1129        let i22 = list.add(Job::new(Pid(22)));
1130        let i23 = list.add(Job::new(Pid(23)));
1131        let i24 = list.add(Job::new(Pid(24)));
1132        let i25 = list.add(Job::new(Pid(25)));
1133        let i26 = list.add(Job::new(Pid(26)));
1134        list.remove(i23).unwrap();
1135
1136        let mut i = list.extract_if(|index, mut job| {
1137            assert_ne!(index, i23);
1138            if index % 2 == 0 {
1139                job.state_reported();
1140            }
1141            index == 0 || job.pid == Pid(26)
1142        });
1143
1144        let mut expected_job_21 = Job::new(Pid(21));
1145        expected_job_21.state_changed = false;
1146        assert_eq!(i.next(), Some((i21, expected_job_21)));
1147        assert_eq!(i.next(), Some((i26, Job::new(Pid(26)))));
1148        assert_eq!(i.next(), None);
1149        assert_eq!(i.next(), None); // ExtractIf is fused.
1150
1151        let indices: Vec<usize> = list.iter().map(|(index, _)| index).collect();
1152        assert_eq!(indices, [i22, i24, i25]);
1153        assert!(list[i22].state_changed);
1154        assert!(list[i24].state_changed);
1155        assert!(!list[i25].state_changed);
1156    }
1157
1158    #[test]
1159    #[allow(clippy::bool_assert_comparison)]
1160    fn updating_job_status_without_expected_state() {
1161        let mut list = JobList::default();
1162        let state = ProcessState::exited(15);
1163        assert_eq!(list.update_status(Pid(20), state), None);
1164
1165        let i10 = list.add(Job::new(Pid(10)));
1166        let i20 = list.add(Job::new(Pid(20)));
1167        let i30 = list.add(Job::new(Pid(30)));
1168        assert_eq!(list[i20].state, ProcessState::Running);
1169
1170        list.get_mut(i20).unwrap().state_reported();
1171        assert_eq!(list[i20].state_changed, false);
1172
1173        assert_eq!(list.update_status(Pid(20), state), Some(i20));
1174        assert_eq!(list[i20].state, ProcessState::exited(15));
1175        assert_eq!(list[i20].state_changed, true);
1176
1177        assert_eq!(list[i10].state, ProcessState::Running);
1178        assert_eq!(list[i30].state, ProcessState::Running);
1179    }
1180
1181    #[test]
1182    #[allow(clippy::bool_assert_comparison)]
1183    fn updating_job_status_with_matching_expected_state() {
1184        let mut list = JobList::default();
1185        let pid = Pid(20);
1186        let mut job = Job::new(pid);
1187        job.expected_state = Some(ProcessState::Running);
1188        job.state_changed = false;
1189        let i20 = list.add(job);
1190
1191        assert_eq!(list.update_status(pid, ProcessState::Running), Some(i20));
1192
1193        let job = &list[i20];
1194        assert_eq!(job.state, ProcessState::Running);
1195        assert_eq!(job.expected_state, None);
1196        assert_eq!(job.state_changed, false);
1197    }
1198
1199    #[test]
1200    #[allow(clippy::bool_assert_comparison)]
1201    fn updating_job_status_with_unmatched_expected_state() {
1202        let mut list = JobList::default();
1203        let pid = Pid(20);
1204        let mut job = Job::new(pid);
1205        job.expected_state = Some(ProcessState::Running);
1206        job.state_changed = false;
1207        let i20 = list.add(job);
1208
1209        let result = list.update_status(pid, ProcessState::exited(0));
1210        assert_eq!(result, Some(i20));
1211
1212        let job = &list[i20];
1213        assert_eq!(job.state, ProcessState::exited(0));
1214        assert_eq!(job.expected_state, None);
1215        assert_eq!(job.state_changed, true);
1216    }
1217
1218    #[test]
1219    #[allow(clippy::bool_assert_comparison)]
1220    fn disowning_jobs() {
1221        let mut list = JobList::default();
1222        let i10 = list.add(Job::new(Pid(10)));
1223        let i20 = list.add(Job::new(Pid(20)));
1224        let i30 = list.add(Job::new(Pid(30)));
1225
1226        list.disown_all();
1227
1228        assert_eq!(list[i10].is_owned, false);
1229        assert_eq!(list[i20].is_owned, false);
1230        assert_eq!(list[i30].is_owned, false);
1231    }
1232
1233    #[test]
1234    fn no_current_and_previous_job_in_empty_job_list() {
1235        let list = JobList::default();
1236        assert_eq!(list.current_job(), None);
1237        assert_eq!(list.previous_job(), None);
1238    }
1239
1240    #[test]
1241    fn current_and_previous_job_in_job_list_with_one_job() {
1242        let mut list = JobList::default();
1243        let i10 = list.add(Job::new(Pid(10)));
1244        assert_eq!(list.current_job(), Some(i10));
1245        assert_eq!(list.previous_job(), None);
1246    }
1247
1248    #[test]
1249    fn current_and_previous_job_in_job_list_with_two_job() {
1250        // If one job is suspended and the other is not, the current job is the
1251        // suspended one.
1252        let mut list = JobList::default();
1253        let mut suspended = Job::new(Pid(10));
1254        suspended.state = ProcessState::stopped(SIGSTOP);
1255        let running = Job::new(Pid(20));
1256        let i10 = list.add(suspended.clone());
1257        let i20 = list.add(running.clone());
1258        assert_eq!(list.current_job(), Some(i10));
1259        assert_eq!(list.previous_job(), Some(i20));
1260
1261        // The order of adding jobs does not matter in this case.
1262        list = JobList::default();
1263        let i20 = list.add(running);
1264        let i10 = list.add(suspended);
1265        assert_eq!(list.current_job(), Some(i10));
1266        assert_eq!(list.previous_job(), Some(i20));
1267    }
1268
1269    #[test]
1270    fn adding_suspended_job_with_running_current_and_previous_job() {
1271        let mut list = JobList::default();
1272        let running_1 = Job::new(Pid(11));
1273        let running_2 = Job::new(Pid(12));
1274        list.add(running_1);
1275        list.add(running_2);
1276        let ex_current_job_index = list.current_job().unwrap();
1277        let ex_previous_job_index = list.previous_job().unwrap();
1278        assert_ne!(ex_current_job_index, ex_previous_job_index);
1279
1280        let mut suspended = Job::new(Pid(20));
1281        suspended.state = ProcessState::stopped(SIGSTOP);
1282        let i20 = list.add(suspended);
1283        let now_current_job_index = list.current_job().unwrap();
1284        let now_previous_job_index = list.previous_job().unwrap();
1285        assert_eq!(now_current_job_index, i20);
1286        assert_eq!(now_previous_job_index, ex_current_job_index);
1287    }
1288
1289    #[test]
1290    fn adding_suspended_job_with_suspended_current_and_running_previous_job() {
1291        let mut list = JobList::default();
1292
1293        let running = Job::new(Pid(18));
1294        let i18 = list.add(running);
1295
1296        let mut suspended_1 = Job::new(Pid(19));
1297        suspended_1.state = ProcessState::stopped(SIGSTOP);
1298        let i19 = list.add(suspended_1);
1299
1300        let ex_current_job_index = list.current_job().unwrap();
1301        let ex_previous_job_index = list.previous_job().unwrap();
1302        assert_eq!(ex_current_job_index, i19);
1303        assert_eq!(ex_previous_job_index, i18);
1304
1305        let mut suspended_2 = Job::new(Pid(20));
1306        suspended_2.state = ProcessState::stopped(SIGSTOP);
1307        let i20 = list.add(suspended_2);
1308
1309        let now_current_job_index = list.current_job().unwrap();
1310        let now_previous_job_index = list.previous_job().unwrap();
1311        assert_eq!(now_current_job_index, ex_current_job_index);
1312        assert_eq!(now_previous_job_index, i20);
1313    }
1314
1315    #[test]
1316    fn removing_current_job() {
1317        let mut list = JobList::default();
1318
1319        let running = Job::new(Pid(10));
1320        let i10 = list.add(running);
1321
1322        let mut suspended_1 = Job::new(Pid(11));
1323        let mut suspended_2 = Job::new(Pid(12));
1324        let mut suspended_3 = Job::new(Pid(13));
1325        suspended_1.state = ProcessState::stopped(SIGSTOP);
1326        suspended_2.state = ProcessState::stopped(SIGSTOP);
1327        suspended_3.state = ProcessState::stopped(SIGSTOP);
1328        list.add(suspended_1);
1329        list.add(suspended_2);
1330        list.add(suspended_3);
1331
1332        let current_job_index_1 = list.current_job().unwrap();
1333        let previous_job_index_1 = list.previous_job().unwrap();
1334        assert_ne!(current_job_index_1, i10);
1335        assert_ne!(previous_job_index_1, i10);
1336
1337        list.remove(current_job_index_1);
1338        let current_job_index_2 = list.current_job().unwrap();
1339        let previous_job_index_2 = list.previous_job().unwrap();
1340        assert_eq!(current_job_index_2, previous_job_index_1);
1341        assert_ne!(previous_job_index_2, current_job_index_2);
1342        // The new previous job is chosen from suspended jobs other than the current job.
1343        let previous_job_2 = &list[previous_job_index_2];
1344        assert!(
1345            previous_job_2.is_suspended(),
1346            "previous_job_2 = {previous_job_2:?}"
1347        );
1348
1349        list.remove(current_job_index_2);
1350        let current_job_index_3 = list.current_job().unwrap();
1351        let previous_job_index_3 = list.previous_job().unwrap();
1352        assert_eq!(current_job_index_3, previous_job_index_2);
1353        // There is no other suspended job, so the new previous job is a running job.
1354        assert_eq!(previous_job_index_3, i10);
1355
1356        list.remove(current_job_index_3);
1357        let current_job_index_4 = list.current_job().unwrap();
1358        assert_eq!(current_job_index_4, i10);
1359        // No more job to be selected for the previous job.
1360        assert_eq!(list.previous_job(), None);
1361    }
1362
1363    #[test]
1364    fn removing_previous_job_with_suspended_job() {
1365        let mut list = JobList::default();
1366
1367        let running = Job::new(Pid(10));
1368        let i10 = list.add(running);
1369
1370        let mut suspended_1 = Job::new(Pid(11));
1371        let mut suspended_2 = Job::new(Pid(12));
1372        let mut suspended_3 = Job::new(Pid(13));
1373        suspended_1.state = ProcessState::stopped(SIGSTOP);
1374        suspended_2.state = ProcessState::stopped(SIGSTOP);
1375        suspended_3.state = ProcessState::stopped(SIGSTOP);
1376        list.add(suspended_1);
1377        list.add(suspended_2);
1378        list.add(suspended_3);
1379
1380        let ex_current_job_index = list.current_job().unwrap();
1381        let ex_previous_job_index = list.previous_job().unwrap();
1382        assert_ne!(ex_current_job_index, i10);
1383        assert_ne!(ex_previous_job_index, i10);
1384
1385        list.remove(ex_previous_job_index);
1386        let now_current_job_index = list.current_job().unwrap();
1387        let now_previous_job_index = list.previous_job().unwrap();
1388        assert_eq!(now_current_job_index, ex_current_job_index);
1389        assert_ne!(now_previous_job_index, now_current_job_index);
1390        // The new previous job is chosen from suspended jobs other than the current job.
1391        let now_previous_job = &list[now_previous_job_index];
1392        assert!(
1393            now_previous_job.is_suspended(),
1394            "now_previous_job = {now_previous_job:?}"
1395        );
1396    }
1397
1398    #[test]
1399    fn removing_previous_job_with_running_job() {
1400        let mut list = JobList::default();
1401
1402        let running = Job::new(Pid(10));
1403        let i10 = list.add(running);
1404
1405        let mut suspended_1 = Job::new(Pid(11));
1406        let mut suspended_2 = Job::new(Pid(12));
1407        suspended_1.state = ProcessState::stopped(SIGSTOP);
1408        suspended_2.state = ProcessState::stopped(SIGSTOP);
1409        list.add(suspended_1);
1410        list.add(suspended_2);
1411
1412        let ex_current_job_index = list.current_job().unwrap();
1413        let ex_previous_job_index = list.previous_job().unwrap();
1414        assert_ne!(ex_current_job_index, i10);
1415        assert_ne!(ex_previous_job_index, i10);
1416
1417        list.remove(ex_previous_job_index);
1418        let now_current_job_index = list.current_job().unwrap();
1419        let now_previous_job_index = list.previous_job().unwrap();
1420        assert_eq!(now_current_job_index, ex_current_job_index);
1421        // When there is no suspended job other than the current job,
1422        // then the new previous job can be any job other than the current.
1423        assert_eq!(now_previous_job_index, i10);
1424    }
1425
1426    #[test]
1427    fn set_current_job_with_running_jobs_only() {
1428        let mut list = JobList::default();
1429        let i21 = list.add(Job::new(Pid(21)));
1430        let i22 = list.add(Job::new(Pid(22)));
1431
1432        assert_eq!(list.set_current_job(i21), Ok(()));
1433        assert_eq!(list.current_job(), Some(i21));
1434
1435        assert_eq!(list.set_current_job(i22), Ok(()));
1436        assert_eq!(list.current_job(), Some(i22));
1437    }
1438
1439    #[test]
1440    fn set_current_job_to_suspended_job() {
1441        let mut list = JobList::default();
1442        list.add(Job::new(Pid(20)));
1443
1444        let mut suspended_1 = Job::new(Pid(21));
1445        let mut suspended_2 = Job::new(Pid(22));
1446        suspended_1.state = ProcessState::stopped(SIGSTOP);
1447        suspended_2.state = ProcessState::stopped(SIGSTOP);
1448        let i21 = list.add(suspended_1);
1449        let i22 = list.add(suspended_2);
1450
1451        assert_eq!(list.set_current_job(i21), Ok(()));
1452        assert_eq!(list.current_job(), Some(i21));
1453
1454        assert_eq!(list.set_current_job(i22), Ok(()));
1455        assert_eq!(list.current_job(), Some(i22));
1456    }
1457
1458    #[test]
1459    fn set_current_job_no_such_job() {
1460        let mut list = JobList::default();
1461        assert_eq!(list.set_current_job(0), Err(SetCurrentJobError::NoSuchJob));
1462        assert_eq!(list.set_current_job(1), Err(SetCurrentJobError::NoSuchJob));
1463        assert_eq!(list.set_current_job(2), Err(SetCurrentJobError::NoSuchJob));
1464    }
1465
1466    #[test]
1467    fn set_current_job_not_suspended() {
1468        let mut list = JobList::default();
1469        let mut suspended = Job::new(Pid(10));
1470        suspended.state = ProcessState::stopped(SIGTSTP);
1471        let running = Job::new(Pid(20));
1472        let i10 = list.add(suspended);
1473        let i20 = list.add(running);
1474        assert_eq!(
1475            list.set_current_job(i20),
1476            Err(SetCurrentJobError::NotSuspended)
1477        );
1478        assert_eq!(list.current_job(), Some(i10));
1479    }
1480
1481    #[test]
1482    fn set_current_job_no_change() {
1483        let mut list = JobList::default();
1484        list.add(Job::new(Pid(5)));
1485        list.add(Job::new(Pid(6)));
1486        let old_current_job_index = list.current_job().unwrap();
1487        let old_previous_job_index = list.previous_job().unwrap();
1488        list.set_current_job(old_current_job_index).unwrap();
1489        let new_current_job_index = list.current_job().unwrap();
1490        let new_previous_job_index = list.previous_job().unwrap();
1491        assert_eq!(new_current_job_index, old_current_job_index);
1492        assert_eq!(new_previous_job_index, old_previous_job_index);
1493    }
1494
1495    #[test]
1496    fn resuming_current_job_without_other_suspended_jobs() {
1497        let mut list = JobList::default();
1498        let mut suspended = Job::new(Pid(10));
1499        suspended.state = ProcessState::stopped(SIGTSTP);
1500        let running = Job::new(Pid(20));
1501        let i10 = list.add(suspended);
1502        let i20 = list.add(running);
1503        list.update_status(Pid(10), ProcessState::Running);
1504        assert_eq!(list.current_job(), Some(i10));
1505        assert_eq!(list.previous_job(), Some(i20));
1506    }
1507
1508    #[test]
1509    fn resuming_current_job_with_another_suspended_job() {
1510        let mut list = JobList::default();
1511        let mut suspended_1 = Job::new(Pid(10));
1512        let mut suspended_2 = Job::new(Pid(20));
1513        suspended_1.state = ProcessState::stopped(SIGTSTP);
1514        suspended_2.state = ProcessState::stopped(SIGTSTP);
1515        let i10 = list.add(suspended_1);
1516        let i20 = list.add(suspended_2);
1517        list.set_current_job(i10).unwrap();
1518        list.update_status(Pid(10), ProcessState::Running);
1519        // The current job must be a suspended job, if any.
1520        assert_eq!(list.current_job(), Some(i20));
1521        assert_eq!(list.previous_job(), Some(i10));
1522    }
1523
1524    #[test]
1525    fn resuming_current_job_with_other_suspended_jobs() {
1526        let mut list = JobList::default();
1527        let mut suspended_1 = Job::new(Pid(10));
1528        let mut suspended_2 = Job::new(Pid(20));
1529        let mut suspended_3 = Job::new(Pid(30));
1530        suspended_1.state = ProcessState::stopped(SIGTSTP);
1531        suspended_2.state = ProcessState::stopped(SIGTSTP);
1532        suspended_3.state = ProcessState::stopped(SIGTSTP);
1533        list.add(suspended_1);
1534        list.add(suspended_2);
1535        list.add(suspended_3);
1536        let ex_current_job_pid = list[list.current_job().unwrap()].pid;
1537        let ex_previous_job_index = list.previous_job().unwrap();
1538
1539        list.update_status(ex_current_job_pid, ProcessState::Running);
1540        let now_current_job_index = list.current_job().unwrap();
1541        let now_previous_job_index = list.previous_job().unwrap();
1542        assert_eq!(now_current_job_index, ex_previous_job_index);
1543        assert_ne!(now_previous_job_index, now_current_job_index);
1544        // The new previous job is chosen from suspended jobs other than the current job.
1545        let now_previous_job = &list[now_previous_job_index];
1546        assert!(
1547            now_previous_job.is_suspended(),
1548            "now_previous_job = {now_previous_job:?}"
1549        );
1550    }
1551
1552    #[test]
1553    fn resuming_previous_job() {
1554        let mut list = JobList::default();
1555        let mut suspended_1 = Job::new(Pid(10));
1556        let mut suspended_2 = Job::new(Pid(20));
1557        let mut suspended_3 = Job::new(Pid(30));
1558        suspended_1.state = ProcessState::stopped(SIGTSTP);
1559        suspended_2.state = ProcessState::stopped(SIGTSTP);
1560        suspended_3.state = ProcessState::stopped(SIGTSTP);
1561        list.add(suspended_1);
1562        list.add(suspended_2);
1563        list.add(suspended_3);
1564        let ex_current_job_index = list.current_job().unwrap();
1565        let ex_previous_job_pid = list[list.previous_job().unwrap()].pid;
1566
1567        list.update_status(ex_previous_job_pid, ProcessState::Running);
1568        let now_current_job_index = list.current_job().unwrap();
1569        let now_previous_job_index = list.previous_job().unwrap();
1570        assert_eq!(now_current_job_index, ex_current_job_index);
1571        assert_ne!(now_previous_job_index, now_current_job_index);
1572        // The new previous job is chosen from suspended jobs other than the current job.
1573        let now_previous_job = &list[now_previous_job_index];
1574        assert!(
1575            now_previous_job.is_suspended(),
1576            "now_previous_job = {now_previous_job:?}"
1577        );
1578    }
1579
1580    #[test]
1581    fn resuming_other_job() {
1582        let mut list = JobList::default();
1583        let mut suspended_1 = Job::new(Pid(10));
1584        let mut suspended_2 = Job::new(Pid(20));
1585        let mut suspended_3 = Job::new(Pid(30));
1586        suspended_1.state = ProcessState::stopped(SIGTSTP);
1587        suspended_2.state = ProcessState::stopped(SIGTSTP);
1588        suspended_3.state = ProcessState::stopped(SIGTSTP);
1589        let i10 = list.add(suspended_1);
1590        let i20 = list.add(suspended_2);
1591        let _i30 = list.add(suspended_3);
1592        list.set_current_job(i20).unwrap();
1593        list.set_current_job(i10).unwrap();
1594        list.update_status(Pid(30), ProcessState::Running);
1595        assert_eq!(list.current_job(), Some(i10));
1596        assert_eq!(list.previous_job(), Some(i20));
1597    }
1598
1599    #[test]
1600    fn suspending_current_job() {
1601        let mut list = JobList::default();
1602        let i11 = list.add(Job::new(Pid(11)));
1603        let i12 = list.add(Job::new(Pid(12)));
1604        list.set_current_job(i11).unwrap();
1605        list.update_status(Pid(11), ProcessState::stopped(SIGTTOU));
1606        assert_eq!(list.current_job(), Some(i11));
1607        assert_eq!(list.previous_job(), Some(i12));
1608    }
1609
1610    #[test]
1611    fn suspending_previous_job() {
1612        let mut list = JobList::default();
1613        let i11 = list.add(Job::new(Pid(11)));
1614        let i12 = list.add(Job::new(Pid(12)));
1615        list.set_current_job(i11).unwrap();
1616        list.update_status(Pid(12), ProcessState::stopped(SIGTTOU));
1617        assert_eq!(list.current_job(), Some(i12));
1618        assert_eq!(list.previous_job(), Some(i11));
1619    }
1620
1621    #[test]
1622    fn suspending_job_with_running_current_job() {
1623        let mut list = JobList::default();
1624        let i10 = list.add(Job::new(Pid(10)));
1625        let _i11 = list.add(Job::new(Pid(11)));
1626        let i12 = list.add(Job::new(Pid(12)));
1627        list.set_current_job(i10).unwrap();
1628        list.update_status(Pid(12), ProcessState::stopped(SIGTTIN));
1629        assert_eq!(list.current_job(), Some(i12));
1630        assert_eq!(list.previous_job(), Some(i10));
1631    }
1632
1633    #[test]
1634    fn suspending_job_with_running_previous_job() {
1635        let mut list = JobList::default();
1636        let i11 = list.add(Job::new(Pid(11)));
1637        let i12 = list.add(Job::new(Pid(12)));
1638        let mut suspended = Job::new(Pid(10));
1639        suspended.state = ProcessState::stopped(SIGTTIN);
1640        let i10 = list.add(suspended);
1641        assert_eq!(list.current_job(), Some(i10));
1642        assert_eq!(list.previous_job(), Some(i11));
1643
1644        list.update_status(Pid(12), ProcessState::stopped(SIGTTOU));
1645        assert_eq!(list.current_job(), Some(i12));
1646        assert_eq!(list.previous_job(), Some(i10));
1647    }
1648
1649    // TODO tcsetpgrp_with_block tests
1650    // TODO tcsetpgrp_without_block tests
1651
1652    #[test]
1653    fn do_not_add_job_if_exited() {
1654        let mut env = Env::new_virtual();
1655        let result = add_job_if_suspended(
1656            &mut env,
1657            Pid(123),
1658            ProcessResult::Exited(ExitStatus(42)),
1659            || "foo".to_string(),
1660        );
1661        assert_eq!(result, Continue(ExitStatus(42)));
1662        assert_eq!(env.jobs.len(), 0);
1663    }
1664
1665    #[test]
1666    fn do_not_add_job_if_signaled() {
1667        let mut env = Env::new_virtual();
1668        let signal = signal::Number::from_raw_unchecked(NonZero::new(42).unwrap());
1669        let result = add_job_if_suspended(
1670            &mut env,
1671            Pid(123),
1672            ProcessResult::Signaled {
1673                signal,
1674                core_dump: false,
1675            },
1676            || "foo".to_string(),
1677        );
1678        assert_eq!(result, Continue(ExitStatus::from(signal)));
1679        assert_eq!(env.jobs.len(), 0);
1680    }
1681
1682    #[test]
1683    fn add_job_if_stopped() {
1684        let mut env = Env::new_virtual();
1685        let signal = signal::Number::from_raw_unchecked(NonZero::new(42).unwrap());
1686        let process_result = ProcessResult::Stopped(signal);
1687        let result = add_job_if_suspended(&mut env, Pid(123), process_result, || "foo".to_string());
1688        assert_eq!(result, Continue(ExitStatus::from(signal)));
1689        assert_eq!(env.jobs.len(), 1);
1690        let job = env.jobs.get(0).unwrap();
1691        assert_eq!(job.pid, Pid(123));
1692        assert!(job.job_controlled);
1693        assert_eq!(job.state, ProcessState::Halted(process_result));
1694        assert_eq!(job.name, "foo");
1695    }
1696
1697    #[test]
1698    fn break_if_stopped_and_interactive() {
1699        let mut env = Env::new_virtual();
1700        env.options.set(Interactive, On);
1701        let signal = signal::Number::from_raw_unchecked(NonZero::new(42).unwrap());
1702        let process_result = ProcessResult::Stopped(signal);
1703        let result = add_job_if_suspended(&mut env, Pid(123), process_result, || "foo".to_string());
1704        assert_eq!(
1705            result,
1706            Break(Divert::Interrupt(Some(ExitStatus::from(signal))))
1707        );
1708        assert_eq!(env.jobs.len(), 1);
1709    }
1710}