yash_cli/
lib.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//! This is an internal library crate for the yash shell. Currently, **this
18//! crate is not intended to be used as a library by other crates. No part of
19//! this crate is covered by semantic versioning.**
20//!
21//! The entry point for the shell is the [`main`] function, which is to be used
22//! as the `main` function in the binary crate. The function sets up the shell
23//! environment and runs the main read-eval loop.
24
25pub mod startup;
26// mod runner;
27
28use self::startup::args::Parse;
29use self::startup::init_file::run_rcfile;
30use self::startup::input::prepare_input;
31use std::cell::RefCell;
32use std::ops::ControlFlow::{Break, Continue};
33use yash_env::Env;
34use yash_env::RealSystem;
35use yash_env::System;
36use yash_env::option::{Interactive, On};
37use yash_env::signal;
38use yash_env::system::{Disposition, Errno, SystemEx as _};
39use yash_executor::Executor;
40use yash_semantics::trap::run_exit_trap;
41use yash_semantics::{Divert, ExitStatus};
42use yash_semantics::{interactive_read_eval_loop, read_eval_loop};
43
44async fn print_version(env: &mut Env) {
45    let version = env!("CARGO_PKG_VERSION");
46    let result = yash_builtin::common::output(env, &format!("yash {version}\n")).await;
47    env.exit_status = result.exit_status();
48}
49
50// The RefCell is local to this function, so it is safe to keep borrows across await points.
51#[allow(clippy::await_holding_refcell_ref)]
52async fn run_as_shell_process(env: &mut Env) {
53    // Parse the command-line arguments
54    let run = match self::startup::args::parse(std::env::args()) {
55        Ok(Parse::Help) => todo!("print help"),
56        Ok(Parse::Version) => return print_version(env).await,
57        Ok(Parse::Run(run)) => run,
58        Err(e) => {
59            let arg0 = std::env::args().next().unwrap_or_else(|| "yash".to_owned());
60            env.system.print_error(&format!("{arg0}: {e}\n")).await;
61            env.exit_status = ExitStatus::ERROR;
62            return;
63        }
64    };
65
66    // Import environment variables
67    env.variables.extend_env(std::env::vars());
68
69    let work = self::startup::configure_environment(env, run);
70
71    let is_interactive = env.options.get(Interactive) == On;
72
73    // Run initialization files
74    // TODO run profile if login
75    run_rcfile(env, work.rcfile).await;
76
77    // Prepare the input for the main read-eval loop
78    let ref_env = RefCell::new(env);
79    let lexer = match prepare_input(&ref_env, &work.source) {
80        Ok(lexer) => lexer,
81        Err(e) => {
82            let arg0 = std::env::args().next().unwrap_or_else(|| "yash".to_owned());
83            let message = format!("{arg0}: {e}\n");
84            // The borrow checker of Rust 1.79.0 is not smart enough to reason
85            // about the lifetime of `e` here, so we re-borrow from `ref_env`
86            // instead of taking `env` out of `ref_env`.
87            // let mut env = ref_env.into_inner();
88            let mut env = ref_env.borrow_mut();
89            env.system.print_error(&message).await;
90            env.exit_status = match e.errno {
91                Errno::ENOENT | Errno::ENOTDIR | Errno::EILSEQ => ExitStatus::NOT_FOUND,
92                _ => ExitStatus::NOEXEC,
93            };
94            return;
95        }
96    };
97
98    // Run the read-eval loop
99    let result = if is_interactive {
100        interactive_read_eval_loop(&ref_env, &mut { lexer }).await
101    } else {
102        read_eval_loop(&ref_env, &mut { lexer }).await
103    };
104
105    let env = ref_env.into_inner();
106    env.apply_result(result);
107
108    match result {
109        Continue(())
110        | Break(Divert::Continue { .. })
111        | Break(Divert::Break { .. })
112        | Break(Divert::Return(_))
113        | Break(Divert::Interrupt(_))
114        | Break(Divert::Exit(_)) => run_exit_trap(env).await,
115        Break(Divert::Abort(_)) => (),
116    }
117}
118
119pub fn main() -> ! {
120    // SAFETY: This is the only instance of RealSystem we create in the whole
121    // process.
122    let system = unsafe { RealSystem::new() };
123    let mut env = Env::with_system(Box::new(system));
124
125    // Rust by default sets SIGPIPE to SIG_IGN, which is not desired.
126    // As an imperfect workaround, we set SIGPIPE to SIG_DFL here.
127    // TODO Use unix_sigpipe: https://github.com/rust-lang/rust/issues/97889
128    let sigpipe = env
129        .system
130        .signal_number_from_name(signal::Name::Pipe)
131        .unwrap();
132    _ = env.system.sigaction(sigpipe, Disposition::Default);
133
134    let system = env.system.clone();
135    let executor = Executor::new();
136    let task = Box::pin(async {
137        run_as_shell_process(&mut env).await;
138        env.system.exit_or_raise(env.exit_status).await;
139    });
140    // SAFETY: We never create new threads in the whole process, so wakers are
141    // never shared between threads.
142    unsafe { executor.spawn_pinned(task) }
143    loop {
144        executor.run_until_stalled();
145        system.select(false).ok();
146    }
147}