Skip to main content

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, 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 mut old_mask = Vec::new();
944
945    system
946        .sigmask(Some((SigmaskOp::Add, &[S::SIGTTOU])), Some(&mut old_mask))
947        .await?;
948
949    let result = system.tcsetpgrp(fd, pgid).await;
950
951    let result_2 = system
952        .sigmask(Some((SigmaskOp::Set, &old_mask)), None)
953        .await;
954
955    result.and(result_2)
956}
957
958/// Switches the foreground process group with the default SIGTTOU settings.
959///
960/// This is a convenience function to ensure the shell has been in the
961/// foreground and optionally change the foreground process group. This
962/// function calls [`Sigaction::sigaction`] to restore the action for
963/// SIGTTOU to the default disposition (which is to suspend the shell
964/// process), [`Sigmask::sigmask`] to unblock SIGTTOU, and
965/// [`TcSetPgrp::tcsetpgrp`] to modify the foreground job. If the calling
966/// process is not in the foreground, `tcsetpgrp` will suspend the process
967/// with SIGTTOU until another job-controlling process resumes it in the
968/// foreground. After `tcsetpgrp` completes, this function calls `sigmask`
969/// and `sigaction` to restore the original state.
970///
971/// Note that if `pgid` is the process group ID of the current process, this
972/// function does not change the foreground job, but the process is still
973/// subject to suspension if it has not been in the foreground.
974///
975/// Use [`tcsetpgrp_with_block`] to change the job even if the current shell is
976/// not in the foreground.
977pub async fn tcsetpgrp_without_block<S>(system: &S, fd: Fd, pgid: Pid) -> crate::system::Result<()>
978where
979    S: Signals + Sigaction + Sigmask + TcSetPgrp + ?Sized,
980{
981    match system.sigaction(S::SIGTTOU, Disposition::Default) {
982        Err(e) => Err(e),
983        Ok(old_handling) => {
984            let mut old_mask = Vec::new();
985            let result = match system
986                .sigmask(
987                    Some((SigmaskOp::Remove, &[S::SIGTTOU])),
988                    Some(&mut old_mask),
989                )
990                .await
991            {
992                Err(e) => Err(e),
993                Ok(()) => {
994                    let result = system.tcsetpgrp(fd, pgid).await;
995
996                    let result_2 = system
997                        .sigmask(Some((SigmaskOp::Set, &old_mask)), None)
998                        .await;
999
1000                    result.and(result_2)
1001                }
1002            };
1003
1004            let result_2 = system.sigaction(S::SIGTTOU, old_handling).map(drop);
1005
1006            result.and(result_2)
1007        }
1008    }
1009}
1010
1011/// Adds a job if the process is suspended.
1012///
1013/// This is a convenience function for handling the result of
1014/// [`Subshell::start_and_wait`](crate::subshell::Subshell::start_and_wait).
1015///
1016/// If the process result indicates that the process is stopped, this function
1017/// adds a job to the job list in the environment. The job is marked as
1018/// job-controlled and its state is derived from the process result. The job
1019/// name is set to the result of the `name` closure. If the current environment
1020/// is interactive, this function returns
1021/// `Break(Divert::Interrupt(Some(exit_status)))` to indicate that the shell
1022/// should be interrupted.
1023///
1024/// If the process is not stopped, this function does not add a job.
1025///
1026/// Returns the exit status of the process that should be assigned to
1027/// `env.exit_status`.
1028pub fn add_job_if_suspended<S, F>(
1029    env: &mut Env<S>,
1030    pid: Pid,
1031    result: ProcessResult,
1032    name: F,
1033) -> crate::semantics::Result<ExitStatus>
1034where
1035    F: FnOnce() -> String,
1036{
1037    let exit_status = result.into();
1038
1039    if result.is_stopped() {
1040        let mut job = Job::new(pid);
1041        job.job_controlled = true;
1042        job.state = result.into();
1043        job.name = name();
1044        env.jobs.add(job);
1045
1046        if env.is_interactive() {
1047            return Break(Divert::Interrupt(Some(exit_status)));
1048        }
1049    }
1050
1051    Continue(exit_status)
1052}
1053
1054pub mod fmt;
1055pub mod id;
1056
1057#[cfg(test)]
1058mod tests {
1059    use super::*;
1060    use crate::option::Option::Interactive;
1061    use crate::option::State::On;
1062    use crate::signal;
1063    use crate::system::r#virtual::{SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU};
1064    use std::num::NonZero;
1065
1066    #[test]
1067    fn job_list_find_by_pid() {
1068        let mut list = JobList::default();
1069        assert_eq!(list.find_by_pid(Pid(10)), None);
1070
1071        let i10 = list.add(Job::new(Pid(10)));
1072        let i20 = list.add(Job::new(Pid(20)));
1073        let i30 = list.add(Job::new(Pid(30)));
1074        assert_eq!(list.find_by_pid(Pid(10)), Some(i10));
1075        assert_eq!(list.find_by_pid(Pid(20)), Some(i20));
1076        assert_eq!(list.find_by_pid(Pid(30)), Some(i30));
1077        assert_eq!(list.find_by_pid(Pid(40)), None);
1078
1079        list.remove(i10);
1080        assert_eq!(list.find_by_pid(Pid(10)), None);
1081    }
1082
1083    #[test]
1084    fn job_list_add_and_remove() {
1085        // This test case depends on how Slab reuses the index of removed items.
1086        let mut list = JobList::default();
1087
1088        assert_eq!(list.add(Job::new(Pid(10))), 0);
1089        assert_eq!(list.add(Job::new(Pid(11))), 1);
1090        assert_eq!(list.add(Job::new(Pid(12))), 2);
1091
1092        assert_eq!(list.remove(0).unwrap().pid, Pid(10));
1093        assert_eq!(list.remove(1).unwrap().pid, Pid(11));
1094
1095        // Indices are reused in the reverse order of removals.
1096        assert_eq!(list.add(Job::new(Pid(13))), 1);
1097        assert_eq!(list.add(Job::new(Pid(14))), 0);
1098
1099        assert_eq!(list.remove(0).unwrap().pid, Pid(14));
1100        assert_eq!(list.remove(1).unwrap().pid, Pid(13));
1101        assert_eq!(list.remove(2).unwrap().pid, Pid(12));
1102
1103        // Once the job list is empty, indices start from 0 again.
1104        assert_eq!(list.add(Job::new(Pid(13))), 0);
1105        assert_eq!(list.add(Job::new(Pid(14))), 1);
1106    }
1107
1108    #[test]
1109    fn job_list_add_same_pid() {
1110        let mut list = JobList::default();
1111
1112        let mut job = Job::new(Pid(10));
1113        job.name = "first job".to_string();
1114        let i_first = list.add(job);
1115
1116        let mut job = Job::new(Pid(10));
1117        job.name = "second job".to_string();
1118        let i_second = list.add(job);
1119
1120        let job = &list[i_second];
1121        assert_eq!(job.pid, Pid(10));
1122        assert_eq!(job.name, "second job");
1123
1124        assert_ne!(
1125            list.get(i_first).map(|job| job.name.as_str()),
1126            Some("first job")
1127        );
1128    }
1129
1130    #[test]
1131    fn job_list_extract_if() {
1132        let mut list = JobList::default();
1133        let i21 = list.add(Job::new(Pid(21)));
1134        let i22 = list.add(Job::new(Pid(22)));
1135        let i23 = list.add(Job::new(Pid(23)));
1136        let i24 = list.add(Job::new(Pid(24)));
1137        let i25 = list.add(Job::new(Pid(25)));
1138        let i26 = list.add(Job::new(Pid(26)));
1139        list.remove(i23).unwrap();
1140
1141        let mut i = list.extract_if(|index, mut job| {
1142            assert_ne!(index, i23);
1143            if index % 2 == 0 {
1144                job.state_reported();
1145            }
1146            index == 0 || job.pid == Pid(26)
1147        });
1148
1149        let mut expected_job_21 = Job::new(Pid(21));
1150        expected_job_21.state_changed = false;
1151        assert_eq!(i.next(), Some((i21, expected_job_21)));
1152        assert_eq!(i.next(), Some((i26, Job::new(Pid(26)))));
1153        assert_eq!(i.next(), None);
1154        assert_eq!(i.next(), None); // ExtractIf is fused.
1155
1156        let indices: Vec<usize> = list.iter().map(|(index, _)| index).collect();
1157        assert_eq!(indices, [i22, i24, i25]);
1158        assert!(list[i22].state_changed);
1159        assert!(list[i24].state_changed);
1160        assert!(!list[i25].state_changed);
1161    }
1162
1163    #[test]
1164    #[allow(clippy::bool_assert_comparison)]
1165    fn updating_job_status_without_expected_state() {
1166        let mut list = JobList::default();
1167        let state = ProcessState::exited(15);
1168        assert_eq!(list.update_status(Pid(20), state), None);
1169
1170        let i10 = list.add(Job::new(Pid(10)));
1171        let i20 = list.add(Job::new(Pid(20)));
1172        let i30 = list.add(Job::new(Pid(30)));
1173        assert_eq!(list[i20].state, ProcessState::Running);
1174
1175        list.get_mut(i20).unwrap().state_reported();
1176        assert_eq!(list[i20].state_changed, false);
1177
1178        assert_eq!(list.update_status(Pid(20), state), Some(i20));
1179        assert_eq!(list[i20].state, ProcessState::exited(15));
1180        assert_eq!(list[i20].state_changed, true);
1181
1182        assert_eq!(list[i10].state, ProcessState::Running);
1183        assert_eq!(list[i30].state, ProcessState::Running);
1184    }
1185
1186    #[test]
1187    #[allow(clippy::bool_assert_comparison)]
1188    fn updating_job_status_with_matching_expected_state() {
1189        let mut list = JobList::default();
1190        let pid = Pid(20);
1191        let mut job = Job::new(pid);
1192        job.expected_state = Some(ProcessState::Running);
1193        job.state_changed = false;
1194        let i20 = list.add(job);
1195
1196        assert_eq!(list.update_status(pid, ProcessState::Running), Some(i20));
1197
1198        let job = &list[i20];
1199        assert_eq!(job.state, ProcessState::Running);
1200        assert_eq!(job.expected_state, None);
1201        assert_eq!(job.state_changed, false);
1202    }
1203
1204    #[test]
1205    #[allow(clippy::bool_assert_comparison)]
1206    fn updating_job_status_with_unmatched_expected_state() {
1207        let mut list = JobList::default();
1208        let pid = Pid(20);
1209        let mut job = Job::new(pid);
1210        job.expected_state = Some(ProcessState::Running);
1211        job.state_changed = false;
1212        let i20 = list.add(job);
1213
1214        let result = list.update_status(pid, ProcessState::exited(0));
1215        assert_eq!(result, Some(i20));
1216
1217        let job = &list[i20];
1218        assert_eq!(job.state, ProcessState::exited(0));
1219        assert_eq!(job.expected_state, None);
1220        assert_eq!(job.state_changed, true);
1221    }
1222
1223    #[test]
1224    #[allow(clippy::bool_assert_comparison)]
1225    fn disowning_jobs() {
1226        let mut list = JobList::default();
1227        let i10 = list.add(Job::new(Pid(10)));
1228        let i20 = list.add(Job::new(Pid(20)));
1229        let i30 = list.add(Job::new(Pid(30)));
1230
1231        list.disown_all();
1232
1233        assert_eq!(list[i10].is_owned, false);
1234        assert_eq!(list[i20].is_owned, false);
1235        assert_eq!(list[i30].is_owned, false);
1236    }
1237
1238    #[test]
1239    fn no_current_and_previous_job_in_empty_job_list() {
1240        let list = JobList::default();
1241        assert_eq!(list.current_job(), None);
1242        assert_eq!(list.previous_job(), None);
1243    }
1244
1245    #[test]
1246    fn current_and_previous_job_in_job_list_with_one_job() {
1247        let mut list = JobList::default();
1248        let i10 = list.add(Job::new(Pid(10)));
1249        assert_eq!(list.current_job(), Some(i10));
1250        assert_eq!(list.previous_job(), None);
1251    }
1252
1253    #[test]
1254    fn current_and_previous_job_in_job_list_with_two_job() {
1255        // If one job is suspended and the other is not, the current job is the
1256        // suspended one.
1257        let mut list = JobList::default();
1258        let mut suspended = Job::new(Pid(10));
1259        suspended.state = ProcessState::stopped(SIGSTOP);
1260        let running = Job::new(Pid(20));
1261        let i10 = list.add(suspended.clone());
1262        let i20 = list.add(running.clone());
1263        assert_eq!(list.current_job(), Some(i10));
1264        assert_eq!(list.previous_job(), Some(i20));
1265
1266        // The order of adding jobs does not matter in this case.
1267        list = JobList::default();
1268        let i20 = list.add(running);
1269        let i10 = list.add(suspended);
1270        assert_eq!(list.current_job(), Some(i10));
1271        assert_eq!(list.previous_job(), Some(i20));
1272    }
1273
1274    #[test]
1275    fn adding_suspended_job_with_running_current_and_previous_job() {
1276        let mut list = JobList::default();
1277        let running_1 = Job::new(Pid(11));
1278        let running_2 = Job::new(Pid(12));
1279        list.add(running_1);
1280        list.add(running_2);
1281        let ex_current_job_index = list.current_job().unwrap();
1282        let ex_previous_job_index = list.previous_job().unwrap();
1283        assert_ne!(ex_current_job_index, ex_previous_job_index);
1284
1285        let mut suspended = Job::new(Pid(20));
1286        suspended.state = ProcessState::stopped(SIGSTOP);
1287        let i20 = list.add(suspended);
1288        let now_current_job_index = list.current_job().unwrap();
1289        let now_previous_job_index = list.previous_job().unwrap();
1290        assert_eq!(now_current_job_index, i20);
1291        assert_eq!(now_previous_job_index, ex_current_job_index);
1292    }
1293
1294    #[test]
1295    fn adding_suspended_job_with_suspended_current_and_running_previous_job() {
1296        let mut list = JobList::default();
1297
1298        let running = Job::new(Pid(18));
1299        let i18 = list.add(running);
1300
1301        let mut suspended_1 = Job::new(Pid(19));
1302        suspended_1.state = ProcessState::stopped(SIGSTOP);
1303        let i19 = list.add(suspended_1);
1304
1305        let ex_current_job_index = list.current_job().unwrap();
1306        let ex_previous_job_index = list.previous_job().unwrap();
1307        assert_eq!(ex_current_job_index, i19);
1308        assert_eq!(ex_previous_job_index, i18);
1309
1310        let mut suspended_2 = Job::new(Pid(20));
1311        suspended_2.state = ProcessState::stopped(SIGSTOP);
1312        let i20 = list.add(suspended_2);
1313
1314        let now_current_job_index = list.current_job().unwrap();
1315        let now_previous_job_index = list.previous_job().unwrap();
1316        assert_eq!(now_current_job_index, ex_current_job_index);
1317        assert_eq!(now_previous_job_index, i20);
1318    }
1319
1320    #[test]
1321    fn removing_current_job() {
1322        let mut list = JobList::default();
1323
1324        let running = Job::new(Pid(10));
1325        let i10 = list.add(running);
1326
1327        let mut suspended_1 = Job::new(Pid(11));
1328        let mut suspended_2 = Job::new(Pid(12));
1329        let mut suspended_3 = Job::new(Pid(13));
1330        suspended_1.state = ProcessState::stopped(SIGSTOP);
1331        suspended_2.state = ProcessState::stopped(SIGSTOP);
1332        suspended_3.state = ProcessState::stopped(SIGSTOP);
1333        list.add(suspended_1);
1334        list.add(suspended_2);
1335        list.add(suspended_3);
1336
1337        let current_job_index_1 = list.current_job().unwrap();
1338        let previous_job_index_1 = list.previous_job().unwrap();
1339        assert_ne!(current_job_index_1, i10);
1340        assert_ne!(previous_job_index_1, i10);
1341
1342        list.remove(current_job_index_1);
1343        let current_job_index_2 = list.current_job().unwrap();
1344        let previous_job_index_2 = list.previous_job().unwrap();
1345        assert_eq!(current_job_index_2, previous_job_index_1);
1346        assert_ne!(previous_job_index_2, current_job_index_2);
1347        // The new previous job is chosen from suspended jobs other than the current job.
1348        let previous_job_2 = &list[previous_job_index_2];
1349        assert!(
1350            previous_job_2.is_suspended(),
1351            "previous_job_2 = {previous_job_2:?}"
1352        );
1353
1354        list.remove(current_job_index_2);
1355        let current_job_index_3 = list.current_job().unwrap();
1356        let previous_job_index_3 = list.previous_job().unwrap();
1357        assert_eq!(current_job_index_3, previous_job_index_2);
1358        // There is no other suspended job, so the new previous job is a running job.
1359        assert_eq!(previous_job_index_3, i10);
1360
1361        list.remove(current_job_index_3);
1362        let current_job_index_4 = list.current_job().unwrap();
1363        assert_eq!(current_job_index_4, i10);
1364        // No more job to be selected for the previous job.
1365        assert_eq!(list.previous_job(), None);
1366    }
1367
1368    #[test]
1369    fn removing_previous_job_with_suspended_job() {
1370        let mut list = JobList::default();
1371
1372        let running = Job::new(Pid(10));
1373        let i10 = list.add(running);
1374
1375        let mut suspended_1 = Job::new(Pid(11));
1376        let mut suspended_2 = Job::new(Pid(12));
1377        let mut suspended_3 = Job::new(Pid(13));
1378        suspended_1.state = ProcessState::stopped(SIGSTOP);
1379        suspended_2.state = ProcessState::stopped(SIGSTOP);
1380        suspended_3.state = ProcessState::stopped(SIGSTOP);
1381        list.add(suspended_1);
1382        list.add(suspended_2);
1383        list.add(suspended_3);
1384
1385        let ex_current_job_index = list.current_job().unwrap();
1386        let ex_previous_job_index = list.previous_job().unwrap();
1387        assert_ne!(ex_current_job_index, i10);
1388        assert_ne!(ex_previous_job_index, i10);
1389
1390        list.remove(ex_previous_job_index);
1391        let now_current_job_index = list.current_job().unwrap();
1392        let now_previous_job_index = list.previous_job().unwrap();
1393        assert_eq!(now_current_job_index, ex_current_job_index);
1394        assert_ne!(now_previous_job_index, now_current_job_index);
1395        // The new previous job is chosen from suspended jobs other than the current job.
1396        let now_previous_job = &list[now_previous_job_index];
1397        assert!(
1398            now_previous_job.is_suspended(),
1399            "now_previous_job = {now_previous_job:?}"
1400        );
1401    }
1402
1403    #[test]
1404    fn removing_previous_job_with_running_job() {
1405        let mut list = JobList::default();
1406
1407        let running = Job::new(Pid(10));
1408        let i10 = list.add(running);
1409
1410        let mut suspended_1 = Job::new(Pid(11));
1411        let mut suspended_2 = Job::new(Pid(12));
1412        suspended_1.state = ProcessState::stopped(SIGSTOP);
1413        suspended_2.state = ProcessState::stopped(SIGSTOP);
1414        list.add(suspended_1);
1415        list.add(suspended_2);
1416
1417        let ex_current_job_index = list.current_job().unwrap();
1418        let ex_previous_job_index = list.previous_job().unwrap();
1419        assert_ne!(ex_current_job_index, i10);
1420        assert_ne!(ex_previous_job_index, i10);
1421
1422        list.remove(ex_previous_job_index);
1423        let now_current_job_index = list.current_job().unwrap();
1424        let now_previous_job_index = list.previous_job().unwrap();
1425        assert_eq!(now_current_job_index, ex_current_job_index);
1426        // When there is no suspended job other than the current job,
1427        // then the new previous job can be any job other than the current.
1428        assert_eq!(now_previous_job_index, i10);
1429    }
1430
1431    #[test]
1432    fn set_current_job_with_running_jobs_only() {
1433        let mut list = JobList::default();
1434        let i21 = list.add(Job::new(Pid(21)));
1435        let i22 = list.add(Job::new(Pid(22)));
1436
1437        assert_eq!(list.set_current_job(i21), Ok(()));
1438        assert_eq!(list.current_job(), Some(i21));
1439
1440        assert_eq!(list.set_current_job(i22), Ok(()));
1441        assert_eq!(list.current_job(), Some(i22));
1442    }
1443
1444    #[test]
1445    fn set_current_job_to_suspended_job() {
1446        let mut list = JobList::default();
1447        list.add(Job::new(Pid(20)));
1448
1449        let mut suspended_1 = Job::new(Pid(21));
1450        let mut suspended_2 = Job::new(Pid(22));
1451        suspended_1.state = ProcessState::stopped(SIGSTOP);
1452        suspended_2.state = ProcessState::stopped(SIGSTOP);
1453        let i21 = list.add(suspended_1);
1454        let i22 = list.add(suspended_2);
1455
1456        assert_eq!(list.set_current_job(i21), Ok(()));
1457        assert_eq!(list.current_job(), Some(i21));
1458
1459        assert_eq!(list.set_current_job(i22), Ok(()));
1460        assert_eq!(list.current_job(), Some(i22));
1461    }
1462
1463    #[test]
1464    fn set_current_job_no_such_job() {
1465        let mut list = JobList::default();
1466        assert_eq!(list.set_current_job(0), Err(SetCurrentJobError::NoSuchJob));
1467        assert_eq!(list.set_current_job(1), Err(SetCurrentJobError::NoSuchJob));
1468        assert_eq!(list.set_current_job(2), Err(SetCurrentJobError::NoSuchJob));
1469    }
1470
1471    #[test]
1472    fn set_current_job_not_suspended() {
1473        let mut list = JobList::default();
1474        let mut suspended = Job::new(Pid(10));
1475        suspended.state = ProcessState::stopped(SIGTSTP);
1476        let running = Job::new(Pid(20));
1477        let i10 = list.add(suspended);
1478        let i20 = list.add(running);
1479        assert_eq!(
1480            list.set_current_job(i20),
1481            Err(SetCurrentJobError::NotSuspended)
1482        );
1483        assert_eq!(list.current_job(), Some(i10));
1484    }
1485
1486    #[test]
1487    fn set_current_job_no_change() {
1488        let mut list = JobList::default();
1489        list.add(Job::new(Pid(5)));
1490        list.add(Job::new(Pid(6)));
1491        let old_current_job_index = list.current_job().unwrap();
1492        let old_previous_job_index = list.previous_job().unwrap();
1493        list.set_current_job(old_current_job_index).unwrap();
1494        let new_current_job_index = list.current_job().unwrap();
1495        let new_previous_job_index = list.previous_job().unwrap();
1496        assert_eq!(new_current_job_index, old_current_job_index);
1497        assert_eq!(new_previous_job_index, old_previous_job_index);
1498    }
1499
1500    #[test]
1501    fn resuming_current_job_without_other_suspended_jobs() {
1502        let mut list = JobList::default();
1503        let mut suspended = Job::new(Pid(10));
1504        suspended.state = ProcessState::stopped(SIGTSTP);
1505        let running = Job::new(Pid(20));
1506        let i10 = list.add(suspended);
1507        let i20 = list.add(running);
1508        list.update_status(Pid(10), ProcessState::Running);
1509        assert_eq!(list.current_job(), Some(i10));
1510        assert_eq!(list.previous_job(), Some(i20));
1511    }
1512
1513    #[test]
1514    fn resuming_current_job_with_another_suspended_job() {
1515        let mut list = JobList::default();
1516        let mut suspended_1 = Job::new(Pid(10));
1517        let mut suspended_2 = Job::new(Pid(20));
1518        suspended_1.state = ProcessState::stopped(SIGTSTP);
1519        suspended_2.state = ProcessState::stopped(SIGTSTP);
1520        let i10 = list.add(suspended_1);
1521        let i20 = list.add(suspended_2);
1522        list.set_current_job(i10).unwrap();
1523        list.update_status(Pid(10), ProcessState::Running);
1524        // The current job must be a suspended job, if any.
1525        assert_eq!(list.current_job(), Some(i20));
1526        assert_eq!(list.previous_job(), Some(i10));
1527    }
1528
1529    #[test]
1530    fn resuming_current_job_with_other_suspended_jobs() {
1531        let mut list = JobList::default();
1532        let mut suspended_1 = Job::new(Pid(10));
1533        let mut suspended_2 = Job::new(Pid(20));
1534        let mut suspended_3 = Job::new(Pid(30));
1535        suspended_1.state = ProcessState::stopped(SIGTSTP);
1536        suspended_2.state = ProcessState::stopped(SIGTSTP);
1537        suspended_3.state = ProcessState::stopped(SIGTSTP);
1538        list.add(suspended_1);
1539        list.add(suspended_2);
1540        list.add(suspended_3);
1541        let ex_current_job_pid = list[list.current_job().unwrap()].pid;
1542        let ex_previous_job_index = list.previous_job().unwrap();
1543
1544        list.update_status(ex_current_job_pid, ProcessState::Running);
1545        let now_current_job_index = list.current_job().unwrap();
1546        let now_previous_job_index = list.previous_job().unwrap();
1547        assert_eq!(now_current_job_index, ex_previous_job_index);
1548        assert_ne!(now_previous_job_index, now_current_job_index);
1549        // The new previous job is chosen from suspended jobs other than the current job.
1550        let now_previous_job = &list[now_previous_job_index];
1551        assert!(
1552            now_previous_job.is_suspended(),
1553            "now_previous_job = {now_previous_job:?}"
1554        );
1555    }
1556
1557    #[test]
1558    fn resuming_previous_job() {
1559        let mut list = JobList::default();
1560        let mut suspended_1 = Job::new(Pid(10));
1561        let mut suspended_2 = Job::new(Pid(20));
1562        let mut suspended_3 = Job::new(Pid(30));
1563        suspended_1.state = ProcessState::stopped(SIGTSTP);
1564        suspended_2.state = ProcessState::stopped(SIGTSTP);
1565        suspended_3.state = ProcessState::stopped(SIGTSTP);
1566        list.add(suspended_1);
1567        list.add(suspended_2);
1568        list.add(suspended_3);
1569        let ex_current_job_index = list.current_job().unwrap();
1570        let ex_previous_job_pid = list[list.previous_job().unwrap()].pid;
1571
1572        list.update_status(ex_previous_job_pid, ProcessState::Running);
1573        let now_current_job_index = list.current_job().unwrap();
1574        let now_previous_job_index = list.previous_job().unwrap();
1575        assert_eq!(now_current_job_index, ex_current_job_index);
1576        assert_ne!(now_previous_job_index, now_current_job_index);
1577        // The new previous job is chosen from suspended jobs other than the current job.
1578        let now_previous_job = &list[now_previous_job_index];
1579        assert!(
1580            now_previous_job.is_suspended(),
1581            "now_previous_job = {now_previous_job:?}"
1582        );
1583    }
1584
1585    #[test]
1586    fn resuming_other_job() {
1587        let mut list = JobList::default();
1588        let mut suspended_1 = Job::new(Pid(10));
1589        let mut suspended_2 = Job::new(Pid(20));
1590        let mut suspended_3 = Job::new(Pid(30));
1591        suspended_1.state = ProcessState::stopped(SIGTSTP);
1592        suspended_2.state = ProcessState::stopped(SIGTSTP);
1593        suspended_3.state = ProcessState::stopped(SIGTSTP);
1594        let i10 = list.add(suspended_1);
1595        let i20 = list.add(suspended_2);
1596        let _i30 = list.add(suspended_3);
1597        list.set_current_job(i20).unwrap();
1598        list.set_current_job(i10).unwrap();
1599        list.update_status(Pid(30), ProcessState::Running);
1600        assert_eq!(list.current_job(), Some(i10));
1601        assert_eq!(list.previous_job(), Some(i20));
1602    }
1603
1604    #[test]
1605    fn suspending_current_job() {
1606        let mut list = JobList::default();
1607        let i11 = list.add(Job::new(Pid(11)));
1608        let i12 = list.add(Job::new(Pid(12)));
1609        list.set_current_job(i11).unwrap();
1610        list.update_status(Pid(11), ProcessState::stopped(SIGTTOU));
1611        assert_eq!(list.current_job(), Some(i11));
1612        assert_eq!(list.previous_job(), Some(i12));
1613    }
1614
1615    #[test]
1616    fn suspending_previous_job() {
1617        let mut list = JobList::default();
1618        let i11 = list.add(Job::new(Pid(11)));
1619        let i12 = list.add(Job::new(Pid(12)));
1620        list.set_current_job(i11).unwrap();
1621        list.update_status(Pid(12), ProcessState::stopped(SIGTTOU));
1622        assert_eq!(list.current_job(), Some(i12));
1623        assert_eq!(list.previous_job(), Some(i11));
1624    }
1625
1626    #[test]
1627    fn suspending_job_with_running_current_job() {
1628        let mut list = JobList::default();
1629        let i10 = list.add(Job::new(Pid(10)));
1630        let _i11 = list.add(Job::new(Pid(11)));
1631        let i12 = list.add(Job::new(Pid(12)));
1632        list.set_current_job(i10).unwrap();
1633        list.update_status(Pid(12), ProcessState::stopped(SIGTTIN));
1634        assert_eq!(list.current_job(), Some(i12));
1635        assert_eq!(list.previous_job(), Some(i10));
1636    }
1637
1638    #[test]
1639    fn suspending_job_with_running_previous_job() {
1640        let mut list = JobList::default();
1641        let i11 = list.add(Job::new(Pid(11)));
1642        let i12 = list.add(Job::new(Pid(12)));
1643        let mut suspended = Job::new(Pid(10));
1644        suspended.state = ProcessState::stopped(SIGTTIN);
1645        let i10 = list.add(suspended);
1646        assert_eq!(list.current_job(), Some(i10));
1647        assert_eq!(list.previous_job(), Some(i11));
1648
1649        list.update_status(Pid(12), ProcessState::stopped(SIGTTOU));
1650        assert_eq!(list.current_job(), Some(i12));
1651        assert_eq!(list.previous_job(), Some(i10));
1652    }
1653
1654    // TODO tcsetpgrp_with_block tests
1655    // TODO tcsetpgrp_without_block tests
1656
1657    #[test]
1658    fn do_not_add_job_if_exited() {
1659        let mut env = Env::new_virtual();
1660        let result = add_job_if_suspended(
1661            &mut env,
1662            Pid(123),
1663            ProcessResult::Exited(ExitStatus(42)),
1664            || "foo".to_string(),
1665        );
1666        assert_eq!(result, Continue(ExitStatus(42)));
1667        assert_eq!(env.jobs.len(), 0);
1668    }
1669
1670    #[test]
1671    fn do_not_add_job_if_signaled() {
1672        let mut env = Env::new_virtual();
1673        let signal = signal::Number::from_raw_unchecked(NonZero::new(42).unwrap());
1674        let result = add_job_if_suspended(
1675            &mut env,
1676            Pid(123),
1677            ProcessResult::Signaled {
1678                signal,
1679                core_dump: false,
1680            },
1681            || "foo".to_string(),
1682        );
1683        assert_eq!(result, Continue(ExitStatus::from(signal)));
1684        assert_eq!(env.jobs.len(), 0);
1685    }
1686
1687    #[test]
1688    fn add_job_if_stopped() {
1689        let mut env = Env::new_virtual();
1690        let signal = signal::Number::from_raw_unchecked(NonZero::new(42).unwrap());
1691        let process_result = ProcessResult::Stopped(signal);
1692        let result = add_job_if_suspended(&mut env, Pid(123), process_result, || "foo".to_string());
1693        assert_eq!(result, Continue(ExitStatus::from(signal)));
1694        assert_eq!(env.jobs.len(), 1);
1695        let job = env.jobs.get(0).unwrap();
1696        assert_eq!(job.pid, Pid(123));
1697        assert!(job.job_controlled);
1698        assert_eq!(job.state, ProcessState::Halted(process_result));
1699        assert_eq!(job.name, "foo");
1700    }
1701
1702    #[test]
1703    fn break_if_stopped_and_interactive() {
1704        let mut env = Env::new_virtual();
1705        env.options.set(Interactive, On);
1706        let signal = signal::Number::from_raw_unchecked(NonZero::new(42).unwrap());
1707        let process_result = ProcessResult::Stopped(signal);
1708        let result = add_job_if_suspended(&mut env, Pid(123), process_result, || "foo".to_string());
1709        assert_eq!(
1710            result,
1711            Break(Divert::Interrupt(Some(ExitStatus::from(signal))))
1712        );
1713        assert_eq!(env.jobs.len(), 1);
1714    }
1715}