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}