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