Skip to main content

yash_env/job/
tcsetpgrp.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//! Items related to the `tcsetpgrp` operation
18
19use super::Pid;
20use crate::io::Fd;
21use crate::signal;
22#[cfg(doc)]
23use crate::system::Concurrent;
24use crate::system::{
25    Disposition, Result, Sigaction, Sigmask, SigmaskOp, Signals, Sigset as _, TcSetPgrp,
26};
27
28/// A trait to run a function with a signal blocked
29///
30/// This trait represents the capability required by [`tcsetpgrp_with_block`] to
31/// run a function with a signal blocked. It is automatically implemented for
32/// any type that implements [`Sigmask`]. Additionally, [`Concurrent`]
33/// implements this trait by delegating to the inner type while not implementing
34/// [`Sigmask`] itself, which allows `Concurrent` to maintain internal
35/// consistency about signal masks while still providing this capability.
36///
37/// This trait defines a higher-level interface to temporarily modify the signal
38/// mask. Typical implementations of this trait will internally depend on
39/// [`Sigmask`] to perform the actual signal mask modification, but the trait
40/// itself does not require this as a supertrait.
41pub trait RunBlocking: Signals {
42    /// Runs the given function with the specified signal blocked.
43    ///
44    /// This function blocks the given signal, runs the given function, and then
45    /// restores the original signal mask. If all operations succeed, the result
46    /// of the function is returned. If any operation fails, an error is
47    /// returned.
48    ///
49    /// This function restores the original signal mask even if the given
50    /// function returns an error, in which case any error restoring the signal
51    /// mask is discarded. If the signal cannot be blocked, this function
52    /// returns an error without running the function.
53    fn run_blocking<F, T>(
54        &self,
55        signal: signal::Number,
56        f: F,
57    ) -> impl Future<Output = Result<T>> + use<'_, Self, F, T>
58    where
59        F: AsyncFnOnce() -> Result<T>;
60}
61
62impl<S> RunBlocking for S
63where
64    S: Sigmask + ?Sized,
65{
66    async fn run_blocking<F, T>(&self, signal: signal::Number, f: F) -> Result<T>
67    where
68        F: AsyncFnOnce() -> Result<T>,
69    {
70        let mut old_mask = S::Sigset::new();
71        self.sigmask(
72            Some((SigmaskOp::Add, &S::Sigset::from_signals([signal])?)),
73            Some(&mut old_mask),
74        )
75        .await?;
76
77        let main_result = f().await;
78
79        let restore_result = self.sigmask(Some((SigmaskOp::Set, &old_mask)), None).await;
80        if main_result.is_ok() {
81            restore_result?;
82        }
83
84        main_result
85    }
86}
87
88/// Switches the foreground process group with SIGTTOU blocked.
89///
90/// This is a convenience function to change the foreground process group
91/// safely. If you call [`TcSetPgrp::tcsetpgrp`] from a background process, the
92/// process is stopped by SIGTTOU by default. To prevent this effect, SIGTTOU
93/// must be blocked or ignored when `tcsetpgrp` is called. This function uses
94/// [`RunBlocking::run_blocking`] to block SIGTTOU while calling `tcsetpgrp`,
95/// which ensures that the shell is not suspended even if it is not in the
96/// foreground.
97///
98/// Use [`tcsetpgrp_without_block`] if you need to make sure the shell is in the
99/// foreground before changing the foreground job.
100pub async fn tcsetpgrp_with_block<S>(system: &S, fd: Fd, pgid: Pid) -> Result<()>
101where
102    S: RunBlocking + TcSetPgrp + ?Sized,
103{
104    system
105        .run_blocking(S::SIGTTOU, || system.tcsetpgrp(fd, pgid))
106        .await
107}
108
109/// A trait to run a function with a signal unblocked and the default disposition
110///
111/// This trait represents the capability required by [`tcsetpgrp_without_block`]
112/// to run a function with a signal unblocked and the default disposition. It is
113/// automatically implemented for any type that implements [`Sigmask`] and
114/// [`Sigaction`]. Additionally, [`Concurrent`] implements this trait by
115/// delegating to the inner type while not implementing [`Sigmask`] or
116/// [`Sigaction`] itself, which allows `Concurrent` to maintain internal
117/// consistency about signal masks and dispositions while still providing this
118/// capability.
119///
120/// This trait defines a higher-level interface to temporarily modify the signal
121/// mask and disposition. Typical implementations of this trait will internally
122/// depend on [`Sigmask`] and [`Sigaction`] to perform the actual signal mask
123/// and disposition modification, but the trait itself does not require them as
124/// supertraits.
125pub trait RunUnblocking: Signals {
126    /// Runs the given function with the specified signal unblocked and the
127    /// default disposition.
128    ///
129    /// For most signals, this function restores the default disposition for the
130    /// given signal, unblocks it, runs the given function, and then restores
131    /// the original signal mask and disposition. If all operations succeed, the
132    /// result of the function is returned. If any operation fails, an error is
133    /// returned.
134    ///
135    /// This function restores the original signal mask and disposition even if
136    /// the given function returns an error, in which case any error restoring
137    /// the signal mask or disposition is discarded. If the signal cannot be
138    /// unblocked or the disposition cannot be changed, this function returns an
139    /// error without running the function.
140    ///
141    /// For signals whose mask or disposition cannot be changed (i.e., [SIGKILL]
142    /// and [SIGSTOP]), this function does not try to unblock the signal or
143    /// restore its default disposition and instead simply runs the given
144    /// function.
145    ///
146    /// [SIGKILL]: crate::system::Signals::SIGKILL
147    /// [SIGSTOP]: crate::system::Signals::SIGSTOP
148    fn run_unblocking<F, T>(
149        &self,
150        signal: signal::Number,
151        f: F,
152    ) -> impl Future<Output = Result<T>> + use<'_, Self, F, T>
153    where
154        F: AsyncFnOnce() -> Result<T>;
155}
156
157impl<S> RunUnblocking for S
158where
159    S: Sigmask + Sigaction + ?Sized,
160{
161    async fn run_unblocking<F, T>(&self, signal: signal::Number, f: F) -> Result<T>
162    where
163        F: AsyncFnOnce() -> Result<T>,
164    {
165        if signal == S::SIGKILL || signal == S::SIGSTOP {
166            // These signals cannot be ignored or blocked, so we just run the function.
167            return f().await;
168        }
169
170        let sigset = S::Sigset::from_signals([signal])?;
171
172        let old_handling = self.sigaction(signal, Disposition::Default)?;
173
174        let mut old_mask = S::Sigset::new();
175        let unblock_result = self
176            .sigmask(Some((SigmaskOp::Remove, &sigset)), Some(&mut old_mask))
177            .await;
178        if let Err(e) = unblock_result {
179            _ = self.sigaction(signal, old_handling);
180            return Err(e);
181        }
182
183        let main_result = f().await;
184
185        let restore_mask_result = self.sigmask(Some((SigmaskOp::Set, &old_mask)), None).await;
186        let restore_action_result = self.sigaction(signal, old_handling);
187
188        if main_result.is_ok() {
189            restore_mask_result?;
190            restore_action_result?;
191        }
192        main_result
193    }
194}
195
196/// Switches the foreground process group with the default SIGTTOU settings.
197///
198/// This is a convenience function to ensure the shell has been in the
199/// foreground and optionally change the foreground process group. If you call
200/// [`TcSetPgrp::tcsetpgrp`] from a background process that has not ignored or
201/// blocked SIGTTOU, the process is stopped by SIGTTOU. This behavior can be
202/// used to ensure the shell is in the foreground before starting job control
203/// operations.
204///
205/// This function temporarily restores the default disposition for SIGTTOU and
206/// unblocks it while calling `tcsetpgrp`, which ensures that the shell is
207/// suspended if it is not in the foreground. The suspended shell must be
208/// resumed by another job-controlling process, after which this function
209/// continues. If the shell is already in the foreground, this function behaves
210/// the same as usual `tcsetpgrp`.
211///
212/// To simply make sure the shell is in the foreground without changing the
213/// foreground job, you can call this function with `pgid` set to the process
214/// group ID of the current process.
215///
216/// Use [`tcsetpgrp_with_block`] to change the job even if the current shell is
217/// not in the foreground.
218pub async fn tcsetpgrp_without_block<S>(system: &S, fd: Fd, pgid: Pid) -> Result<()>
219where
220    S: RunUnblocking + TcSetPgrp + ?Sized,
221{
222    system
223        .run_unblocking(S::SIGTTOU, || system.tcsetpgrp(fd, pgid))
224        .await
225}
226
227#[cfg(test)]
228mod tests {
229    use super::*;
230    use crate::signal;
231    use crate::system::r#virtual::{
232        SIGABRT, SIGALRM, SIGBUS, SIGCHLD, SIGCONT, SIGFPE, SIGHUP, SIGILL, SIGINT, SIGIOT,
233        SIGKILL, SIGPIPE, SIGPROF, SIGQUIT, SIGSEGV, SIGSTOP, SIGSYS, SIGTERM, SIGTRAP, SIGTSTP,
234        SIGTTIN, SIGTTOU, SIGURG, SIGUSR1, SIGUSR2, SIGVTALRM, SIGWINCH, SIGXCPU, SIGXFSZ,
235    };
236    use crate::system::{Errno, GetSigaction};
237    use futures_util::FutureExt as _;
238    use std::cell::{Cell, RefCell};
239    use std::collections::{BTreeMap, BTreeSet};
240    use std::ops::RangeInclusive;
241
242    #[derive(Clone, Debug, Default, Eq, PartialEq)]
243    struct TestSigset(BTreeSet<signal::Number>);
244
245    impl crate::system::Sigset for TestSigset {
246        fn full() -> Self {
247            unimplemented!("not needed for tests")
248        }
249
250        fn insert(&mut self, signal: signal::Number) -> Result<()> {
251            self.0.insert(signal);
252            Ok(())
253        }
254
255        fn remove(&mut self, signal: signal::Number) -> Result<()> {
256            self.0.remove(&signal);
257            Ok(())
258        }
259
260        fn contains(&self, signal: signal::Number) -> Result<bool> {
261            Ok(self.0.contains(&signal))
262        }
263    }
264
265    #[derive(Default)]
266    struct MockSystem {
267        mask: RefCell<TestSigset>,
268        dispositions: RefCell<BTreeMap<signal::Number, Disposition>>,
269        sigmask_errors: RefCell<BTreeMap<usize, Errno>>,
270        sigaction_errors: RefCell<BTreeMap<usize, Errno>>,
271        sigmask_call_count: Cell<usize>,
272        sigaction_call_count: Cell<usize>,
273    }
274
275    impl MockSystem {
276        fn set_mask(&self, mask: TestSigset) {
277            self.mask.replace(mask);
278        }
279
280        fn set_disposition(&self, signal: signal::Number, disposition: Disposition) {
281            self.dispositions.borrow_mut().insert(signal, disposition);
282        }
283
284        fn disposition_of(&self, signal: signal::Number) -> Disposition {
285            self.dispositions
286                .borrow()
287                .get(&signal)
288                .copied()
289                .unwrap_or_default()
290        }
291
292        fn is_blocked(&self, signal: signal::Number) -> bool {
293            self.mask
294                .borrow()
295                .contains(signal)
296                .expect("signals in tests are always valid")
297        }
298
299        fn set_sigmask_error_on_call(&self, call: usize, error: Errno) {
300            self.sigmask_errors.borrow_mut().insert(call, error);
301        }
302
303        fn set_sigaction_error_on_call(&self, call: usize, error: Errno) {
304            self.sigaction_errors.borrow_mut().insert(call, error);
305        }
306    }
307
308    impl Signals for MockSystem {
309        const SIGABRT: signal::Number = SIGABRT;
310        const SIGALRM: signal::Number = SIGALRM;
311        const SIGBUS: signal::Number = SIGBUS;
312        const SIGCHLD: signal::Number = SIGCHLD;
313        const SIGCLD: Option<signal::Number> = None;
314        const SIGCONT: signal::Number = SIGCONT;
315        const SIGEMT: Option<signal::Number> = None;
316        const SIGFPE: signal::Number = SIGFPE;
317        const SIGHUP: signal::Number = SIGHUP;
318        const SIGILL: signal::Number = SIGILL;
319        const SIGINFO: Option<signal::Number> = None;
320        const SIGINT: signal::Number = SIGINT;
321        const SIGIO: Option<signal::Number> = None;
322        const SIGIOT: signal::Number = SIGIOT;
323        const SIGKILL: signal::Number = SIGKILL;
324        const SIGLOST: Option<signal::Number> = None;
325        const SIGPIPE: signal::Number = SIGPIPE;
326        const SIGPOLL: Option<signal::Number> = None;
327        const SIGPROF: signal::Number = SIGPROF;
328        const SIGPWR: Option<signal::Number> = None;
329        const SIGQUIT: signal::Number = SIGQUIT;
330        const SIGSEGV: signal::Number = SIGSEGV;
331        const SIGSTKFLT: Option<signal::Number> = None;
332        const SIGSTOP: signal::Number = SIGSTOP;
333        const SIGSYS: signal::Number = SIGSYS;
334        const SIGTERM: signal::Number = SIGTERM;
335        const SIGTHR: Option<signal::Number> = None;
336        const SIGTRAP: signal::Number = SIGTRAP;
337        const SIGTSTP: signal::Number = SIGTSTP;
338        const SIGTTIN: signal::Number = SIGTTIN;
339        const SIGTTOU: signal::Number = SIGTTOU;
340        const SIGURG: signal::Number = SIGURG;
341        const SIGUSR1: signal::Number = SIGUSR1;
342        const SIGUSR2: signal::Number = SIGUSR2;
343        const SIGVTALRM: signal::Number = SIGVTALRM;
344        const SIGWINCH: signal::Number = SIGWINCH;
345        const SIGXCPU: signal::Number = SIGXCPU;
346        const SIGXFSZ: signal::Number = SIGXFSZ;
347
348        fn sigrt_range(&self) -> Option<RangeInclusive<signal::Number>> {
349            None
350        }
351    }
352
353    impl Sigmask for MockSystem {
354        type Sigset = TestSigset;
355
356        fn sigmask(
357            &self,
358            op: Option<(SigmaskOp, &Self::Sigset)>,
359            old_mask: Option<&mut Self::Sigset>,
360        ) -> impl Future<Output = Result<()>> + use<> {
361            let call_count = self.sigmask_call_count.get() + 1;
362            self.sigmask_call_count.set(call_count);
363
364            if let Some(error) = self.sigmask_errors.borrow_mut().remove(&call_count) {
365                return std::future::ready(Err(error));
366            }
367
368            let result = {
369                let mut mask = self.mask.borrow_mut();
370                if let Some(old_mask) = old_mask {
371                    old_mask.clone_from(&mask);
372                }
373
374                if let Some((op, signals)) = op {
375                    match op {
376                        SigmaskOp::Add => {
377                            for &signal in &signals.0 {
378                                mask.insert(signal).unwrap();
379                            }
380                        }
381                        SigmaskOp::Remove => {
382                            for &signal in &signals.0 {
383                                mask.remove(signal).unwrap();
384                            }
385                        }
386                        SigmaskOp::Set => {
387                            *mask = signals.clone();
388                        }
389                    }
390                }
391
392                Ok(())
393            };
394
395            std::future::ready(result)
396        }
397    }
398
399    impl GetSigaction for MockSystem {
400        fn get_sigaction(&self, signal: signal::Number) -> Result<Disposition> {
401            Ok(self.disposition_of(signal))
402        }
403    }
404
405    impl Sigaction for MockSystem {
406        fn sigaction(&self, signal: signal::Number, action: Disposition) -> Result<Disposition> {
407            let call_count = self.sigaction_call_count.get() + 1;
408            self.sigaction_call_count.set(call_count);
409
410            if let Some(error) = self.sigaction_errors.borrow_mut().remove(&call_count) {
411                return Err(error);
412            }
413
414            Ok(self
415                .dispositions
416                .borrow_mut()
417                .insert(signal, action)
418                .unwrap_or_default())
419        }
420    }
421
422    #[test]
423    fn run_blocking_blocks_signal_and_restores_mask_on_success() {
424        let system = MockSystem::default();
425        let called = Cell::new(false);
426
427        let result = system
428            .run_blocking(MockSystem::SIGTTOU, async || {
429                called.set(true);
430                assert!(system.is_blocked(MockSystem::SIGTTOU));
431                Ok(())
432            })
433            .now_or_never()
434            .unwrap();
435
436        assert_eq!(result, Ok(()));
437        assert!(called.get());
438        assert!(!system.is_blocked(MockSystem::SIGTTOU));
439    }
440
441    #[test]
442    fn run_blocking_does_not_run_function_when_initial_sigmask_fails() {
443        let system = MockSystem::default();
444        system.set_sigmask_error_on_call(1, Errno::EINVAL);
445
446        let result = system
447            .run_blocking(MockSystem::SIGTTOU, async || -> Result<()> {
448                unreachable!("closure should not be called")
449            })
450            .now_or_never()
451            .unwrap();
452
453        assert_eq!(result, Err(Errno::EINVAL));
454    }
455
456    #[test]
457    fn run_blocking_discards_restore_error_when_function_returns_error() {
458        let system = MockSystem::default();
459        system.set_sigmask_error_on_call(2, Errno::EPERM);
460
461        let result = system
462            .run_blocking(MockSystem::SIGTTOU, async || Err::<(), _>(Errno::EINTR))
463            .now_or_never()
464            .unwrap();
465
466        assert_eq!(result, Err(Errno::EINTR));
467    }
468
469    #[test]
470    fn run_blocking_propagates_restore_error_when_function_succeeds() {
471        let system = MockSystem::default();
472        system.set_sigmask_error_on_call(2, Errno::EPERM);
473
474        let result = system
475            .run_blocking(MockSystem::SIGTTOU, async || Ok(()))
476            .now_or_never()
477            .unwrap();
478
479        assert_eq!(result, Err(Errno::EPERM));
480    }
481
482    #[test]
483    fn run_unblocking_for_sigkill_runs_function_without_sigaction_or_sigmask() {
484        let system = MockSystem::default();
485        let called = Cell::new(false);
486
487        let result = system
488            .run_unblocking(MockSystem::SIGKILL, async || {
489                called.set(true);
490                Err::<(), _>(Errno::EINTR)
491            })
492            .now_or_never()
493            .unwrap();
494
495        assert_eq!(result, Err(Errno::EINTR));
496        assert!(called.get());
497        assert_eq!(system.sigmask_call_count.get(), 0);
498        assert_eq!(system.sigaction_call_count.get(), 0);
499    }
500
501    #[test]
502    fn run_unblocking_for_sigstop_runs_function_without_sigaction_or_sigmask() {
503        let system = MockSystem::default();
504        let called = Cell::new(false);
505
506        let result = system
507            .run_unblocking(MockSystem::SIGSTOP, async || {
508                called.set(true);
509                Err::<(), _>(Errno::EINTR)
510            })
511            .now_or_never()
512            .unwrap();
513
514        assert_eq!(result, Err(Errno::EINTR));
515        assert!(called.get());
516        assert_eq!(system.sigmask_call_count.get(), 0);
517        assert_eq!(system.sigaction_call_count.get(), 0);
518    }
519
520    #[test]
521    fn run_unblocking_sets_default_unblocks_and_restores_on_success() {
522        let system = MockSystem::default();
523        let called = Cell::new(false);
524        let mut initial_mask = TestSigset::new();
525        initial_mask.insert(MockSystem::SIGTTOU).unwrap();
526        system.set_mask(initial_mask.clone());
527        system.set_disposition(MockSystem::SIGTTOU, Disposition::Ignore);
528
529        let result = system
530            .run_unblocking(MockSystem::SIGTTOU, async || {
531                called.set(true);
532                assert_eq!(
533                    system.disposition_of(MockSystem::SIGTTOU),
534                    Disposition::Default
535                );
536                assert!(!system.is_blocked(MockSystem::SIGTTOU));
537                Ok(())
538            })
539            .now_or_never()
540            .unwrap();
541
542        assert_eq!(result, Ok(()));
543        assert!(called.get());
544        assert!(system.is_blocked(MockSystem::SIGTTOU));
545        assert_eq!(
546            system.disposition_of(MockSystem::SIGTTOU),
547            Disposition::Ignore
548        );
549    }
550
551    #[test]
552    fn run_unblocking_returns_error_when_unblock_sigmask_fails_and_restores_disposition() {
553        let system = MockSystem::default();
554        system.set_disposition(MockSystem::SIGTTOU, Disposition::Ignore);
555        system.set_sigmask_error_on_call(1, Errno::EPERM);
556
557        let result = system
558            .run_unblocking(MockSystem::SIGTTOU, async || -> Result<()> {
559                unreachable!("closure should not be called")
560            })
561            .now_or_never()
562            .unwrap();
563
564        assert_eq!(result, Err(Errno::EPERM));
565        assert_eq!(
566            system.disposition_of(MockSystem::SIGTTOU),
567            Disposition::Ignore
568        );
569    }
570
571    #[test]
572    fn run_unblocking_discards_restore_errors_when_function_returns_error() {
573        let system = MockSystem::default();
574        let called = Cell::new(false);
575        system.set_sigmask_error_on_call(2, Errno::EPERM);
576        system.set_sigaction_error_on_call(2, Errno::EINVAL);
577
578        let result = system
579            .run_unblocking(MockSystem::SIGTTOU, async || {
580                called.set(true);
581                Err::<(), _>(Errno::EINTR)
582            })
583            .now_or_never()
584            .unwrap();
585
586        assert_eq!(result, Err(Errno::EINTR));
587        assert!(called.get());
588    }
589
590    #[test]
591    fn run_unblocking_propagates_restore_mask_error_when_function_succeeds() {
592        let system = MockSystem::default();
593        system.set_sigmask_error_on_call(2, Errno::EPERM);
594
595        let result = system
596            .run_unblocking(MockSystem::SIGTTOU, async || Ok(()))
597            .now_or_never()
598            .unwrap();
599
600        assert_eq!(result, Err(Errno::EPERM));
601    }
602
603    #[test]
604    fn run_unblocking_restores_sigaction_on_sigmask_restoration_error() {
605        let system = MockSystem::default();
606        system.set_disposition(MockSystem::SIGTTOU, Disposition::Ignore);
607        system.set_sigmask_error_on_call(2, Errno::EPERM);
608
609        let result = system
610            .run_unblocking(MockSystem::SIGTTOU, async || Ok(()))
611            .now_or_never()
612            .unwrap();
613
614        assert_eq!(result, Err(Errno::EPERM));
615        assert_eq!(
616            system.disposition_of(MockSystem::SIGTTOU),
617            Disposition::Ignore
618        );
619    }
620
621    #[test]
622    fn run_unblocking_propagates_restore_action_error_when_function_succeeds() {
623        let system = MockSystem::default();
624        system.set_sigaction_error_on_call(2, Errno::EINVAL);
625
626        let result = system
627            .run_unblocking(MockSystem::SIGTTOU, async || Ok(()))
628            .now_or_never()
629            .unwrap();
630
631        assert_eq!(result, Err(Errno::EINVAL));
632    }
633}