Skip to main content

yash_builtin/
command.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2024 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//! Command built-in
18//!
19//! This module implements the [`command` built-in], which executes a utility
20//! bypassing shell functions.
21//!
22//! [`command` built-in]: https://magicant.github.io/yash-rs/builtins/command.html
23//!
24//! # Implementation notes
25//!
26//! The `-p` option depends on [`Sysconf::confstr_path`] to obtain the standard
27//! search path. See the source code of [`RealSystem::confstr_path`] for the
28//! platforms supported on the real system.
29//!
30//! The built-in depends on some functions injected into the environment's
31//! [`any`](Env::any) storage to perform its operations:
32//!
33//! - An instance of [`RunFunction`] is required to invoke shell functions.
34//! - An instance of [`IsKeyword`] is required to check if an argument word is a
35//!   shell reserved word (keyword).
36//!
37//! If no such instance is found, the built-in will **panic**.
38//!
39//! The [`type`] built-in is equivalent to the `command` built-in with the `-V`
40//! option.
41//!
42//! [`IsKeyword`]: yash_env::parser::IsKeyword
43//! [`RunFunction`]: yash_env::semantics::command::RunFunction
44//! [`type`]: crate::type
45
46use crate::common::report::report_error;
47use enumset::EnumSet;
48use enumset::EnumSetType;
49use yash_env::Env;
50use yash_env::semantics::Field;
51#[cfg(all(doc, unix))]
52use yash_env::system::real::RealSystem;
53use yash_env::system::resource::SetRlimit;
54use yash_env::system::{
55    Close, Dup, Exec, Exit, Fcntl, Fork, Fstat, GetCwd, GetPid, IsExecutableFile, Isatty, Open,
56    SendSignal, SetPgid, ShellPath, Sigaction, Sigmask, Signals, Sysconf, TcSetPgrp, Wait, Write,
57};
58
59/// Category of command name resolution
60///
61/// Used to specify the acceptable categories in [`Search`].
62#[derive(Clone, Copy, Debug, EnumSetType, Eq, Hash, PartialEq)]
63#[enumset(no_super_impls)]
64#[non_exhaustive]
65pub enum Category {
66    Alias,
67    Builtin,
68    ExternalUtility,
69    Function,
70    Keyword,
71}
72
73/// Set of parameters that specify how to resolve a command name
74///
75/// Used in [`Invoke`] and [`Identify`].
76#[derive(Clone, Debug, Eq, Hash, PartialEq)]
77#[non_exhaustive]
78pub struct Search {
79    /// Whether to search for the utility in the standard search path
80    ///
81    /// If `true`, the built-in searches for the utility in the standard search
82    /// path instead of the current `$PATH`. The standard path is obtained from
83    /// TBD.
84    pub standard_path: bool,
85
86    /// Acceptable categories of the command name resolution
87    pub categories: EnumSet<Category>,
88}
89
90impl Search {
91    /// Creates a new `Search` with the default parameters for [`Invoke`].
92    #[must_use]
93    pub fn default_for_invoke() -> Self {
94        Self {
95            standard_path: false,
96            categories: Category::Builtin | Category::ExternalUtility,
97        }
98    }
99
100    /// Creates a new `Search` with the default parameters for [`Identify`].
101    #[must_use]
102    pub fn default_for_identify() -> Self {
103        Self {
104            standard_path: false,
105            categories: EnumSet::all(),
106        }
107    }
108}
109
110/// Parameters to invoke a utility
111#[derive(Clone, Debug, Eq, PartialEq)]
112pub struct Invoke {
113    /// Command name and arguments
114    pub fields: Vec<Field>,
115    /// Search parameters
116    pub search: Search,
117}
118
119impl Default for Invoke {
120    fn default() -> Self {
121        Self {
122            fields: Vec::default(),
123            search: Search::default_for_invoke(),
124        }
125    }
126}
127
128/// Parameters to identify a utility
129#[derive(Clone, Debug, Eq, PartialEq)]
130pub struct Identify {
131    /// Command names
132    pub names: Vec<Field>,
133    /// Search parameters
134    pub search: Search,
135    /// Whether to print a detailed description
136    pub verbose: bool,
137}
138
139impl Default for Identify {
140    fn default() -> Self {
141        Self {
142            names: Vec::default(),
143            search: Search::default_for_identify(),
144            verbose: false,
145        }
146    }
147}
148
149/// Parsed command line arguments of the `command` built-in
150#[derive(Clone, Debug, Eq, PartialEq)]
151#[non_exhaustive]
152pub enum Command {
153    /// Invokes the utility specified by the operands.
154    Invoke(Invoke),
155    /// Identifies the type and location of the utility.
156    Identify(Identify),
157}
158
159impl From<Invoke> for Command {
160    fn from(invoke: Invoke) -> Self {
161        Self::Invoke(invoke)
162    }
163}
164
165impl From<Identify> for Command {
166    fn from(identify: Identify) -> Self {
167        Self::Identify(identify)
168    }
169}
170
171impl Command {
172    pub async fn execute<S>(self, env: &mut Env<S>) -> crate::Result
173    where
174        S: Close
175            + Dup
176            + Exec
177            + Exit
178            + Fcntl
179            + Fork
180            + Fstat
181            + GetCwd
182            + GetPid
183            + IsExecutableFile
184            + Isatty
185            + Open
186            + SendSignal
187            + SetPgid
188            + SetRlimit
189            + ShellPath
190            + Sigaction
191            + Sigmask
192            + Signals
193            + Sysconf
194            + TcSetPgrp
195            + Wait
196            + Write
197            + 'static,
198    {
199        match self {
200            Self::Invoke(invoke) => invoke.execute(env).await,
201            Self::Identify(identify) => identify.execute(env).await,
202        }
203    }
204}
205
206pub mod identify;
207mod invoke;
208pub mod search;
209pub mod syntax;
210
211/// Entry point of the `command` built-in
212///
213/// This function parses the arguments into [`Command`] and executes it.
214pub async fn main<S>(env: &mut Env<S>, args: Vec<Field>) -> crate::Result
215where
216    S: Close
217        + Dup
218        + Exec
219        + Exit
220        + Fcntl
221        + Fork
222        + Fstat
223        + GetCwd
224        + GetPid
225        + IsExecutableFile
226        + Isatty
227        + Open
228        + SendSignal
229        + SetPgid
230        + SetRlimit
231        + ShellPath
232        + Sigaction
233        + Sigmask
234        + Signals
235        + Sysconf
236        + TcSetPgrp
237        + Wait
238        + Write
239        + 'static,
240{
241    match syntax::parse(env, args) {
242        Ok(command) => command.execute(env).await,
243        Err(error) => report_error(env, &error).await,
244    }
245}