yash_cli/
startup.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2023 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//! Shell startup
18
19use self::args::{Run, Source, Work};
20use yash_builtin::BUILTINS;
21use yash_env::Env;
22use yash_env::System;
23use yash_env::io::Fd;
24use yash_env::option::Option::{Interactive, Monitor, Stdin};
25use yash_env::option::State::On;
26
27pub mod args;
28pub mod init_file;
29pub mod input;
30
31/// Tests whether the shell should be implicitly interactive.
32///
33/// As per POSIX, "if the shell reads commands from the standard input and the
34/// shell's standard input and standard error are attached to a terminal, the
35/// shell is considered to be interactive." This function implements this rule.
36///
37/// This function returns `false` if the interactive option is explicitly
38/// specified in the command line arguments to honor the user's intent.
39pub fn auto_interactive<S: System>(system: &S, run: &Run) -> bool {
40    if run.work.source != Source::Stdin {
41        return false;
42    }
43    if run.options.iter().any(|&(o, _)| o == Interactive) {
44        return false;
45    }
46    system.isatty(Fd::STDIN) && system.isatty(Fd::STDERR)
47}
48
49/// Get the environment ready for performing the work.
50///
51/// This function takes the parsed command-line arguments and applies them to
52/// the environment. It also sets up signal dispositions and prepares built-ins
53/// and variables. The function returns the work to be performed, which is
54/// extracted from the `run` argument.
55///
56/// This function is _pure_ in that all system calls are performed by the
57/// `System` trait object (`env.system`).
58pub fn configure_environment(env: &mut Env, run: Run) -> Work {
59    // Apply the parsed options to the environment
60    if auto_interactive(&env.system, &run) {
61        env.options.set(Interactive, On);
62    }
63    if run.work.source == self::args::Source::Stdin {
64        env.options.set(Stdin, On);
65    }
66    for &(option, state) in &run.options {
67        env.options.set(option, state);
68    }
69    if env.options.get(Interactive) == On && !run.options.iter().any(|&(o, _)| o == Monitor) {
70        env.options.set(Monitor, On);
71    }
72
73    // Apply the parsed operands to the environment
74    env.arg0 = run.arg0;
75    env.variables.positional_params_mut().values = run.positional_params;
76
77    // Configure internal dispositions for signals
78    if env.options.get(Interactive) == On {
79        env.traps
80            .enable_internal_dispositions_for_terminators(&mut env.system)
81            .ok();
82        if env.options.get(Monitor) == On {
83            env.traps
84                .enable_internal_dispositions_for_stoppers(&mut env.system)
85                .ok();
86        }
87    }
88
89    // Make sure the shell is in the foreground if job control is enabled
90    if env.options.get(Monitor) == On {
91        // Ignore failures as we can still proceed even if we can't get into the foreground
92        env.ensure_foreground().ok();
93    }
94
95    // Prepare built-ins
96    env.builtins.extend(BUILTINS.iter().cloned());
97
98    // Prepare variables
99    env.init_variables();
100
101    run.work
102}