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