yash_env/builtin.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//! Type definitions for built-in utilities
18//!
19//! This module provides data types for defining built-in utilities.
20//!
21//! Note that concrete implementations of built-ins are not included in the
22//! `yash_env` crate. For implementations of specific built-ins like `cd` and
23//! `export`, see the `yash_builtin` crate.
24
25use crate::Env;
26#[cfg(doc)]
27use crate::semantics::Divert;
28use crate::semantics::ExitStatus;
29use crate::semantics::Field;
30use std::fmt::Debug;
31use std::pin::Pin;
32
33/// Types of built-in utilities
34#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
35pub enum Type {
36 /// Special built-in
37 ///
38 /// Special built-in utilities are built-ins that are defined in [POSIX XCU
39 /// section 2.15](https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_15).
40 ///
41 /// They are treated differently from other built-ins.
42 /// Especially, special built-ins are found in the first stage of command
43 /// search without the `$PATH` search and cannot be overridden by functions
44 /// or external utilities.
45 /// Many errors in special built-ins force the shell to exit.
46 Special,
47
48 /// Standard utility that can be used without `$PATH` search
49 ///
50 /// Mandatory built-ins are utilities that are listed in [POSIX XCU section
51 /// 1.7](https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap01.html#tag_18_07).
52 /// In POSIX, they are called "intrinsic utilities".
53 ///
54 /// Like special built-ins, mandatory built-ins are not subject to `$PATH`
55 /// in command search; They are always found regardless of whether there is
56 /// a corresponding external utility in `$PATH`. However, mandatory
57 /// built-ins can still be overridden by functions.
58 ///
59 /// We call them "mandatory" because POSIX effectively requires them to be
60 /// built into the shell.
61 Mandatory,
62
63 /// Non-portable built-in that can be used without `$PATH` search
64 ///
65 /// Elective built-ins are built-ins that are listed in step 1b of [Command
66 /// Search and Execution](https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_09_01_04)
67 /// in POSIX XCU section 2.9.1.4.
68 /// They are very similar to mandatory built-ins, but their behavior is not
69 /// specified by POSIX, so they are not portable. They cannot be used when
70 /// the (TODO TBD) option is set. <!-- An option that disables non-portable
71 /// behavior would make elective built-ins unusable even if found. An option
72 /// that disables non-conforming behavior would not affect elective
73 /// built-ins. -->
74 ///
75 /// We call them "elective" because it is up to the shell whether to
76 /// implement them.
77 Elective,
78
79 /// Non-conforming extension
80 ///
81 /// Extension built-ins are non-conformant extensions to the POSIX shell.
82 /// Like elective built-ins, they can be executed without `$PATH` search
83 /// finding a corresponding external utility. However, since this behavior
84 /// does not conform to [Command Search and
85 /// Execution](https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_09_01_04)
86 /// in POSIX XCU section 2.9.1.4:
87 ///
88 /// - When the [`PosixlyCorrect`](crate::option::PosixlyCorrect) option is
89 /// on, they are ignored: they are regarded as non-existing utilities so
90 /// that the command search falls through to external utilities.
91 /// - When the (TODO TBD) option is on, they cannot be used even if found
92 /// in command search.
93 Extension,
94
95 /// Built-in that works like a standalone utility
96 ///
97 /// A substitutive built-in is a built-in that is executed instead of an
98 /// external utility to minimize invocation overhead. Since a substitutive
99 /// built-in behaves just as if it were an external utility, it must be
100 /// found in `$PATH` in order to be executed.
101 Substitutive,
102}
103
104/// Names of the special built-in utilities defined by POSIX
105///
106/// This is the fixed list of utility names listed in [POSIX XCU section
107/// 2.15](https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_15).
108/// The names are sorted in ASCII order.
109///
110/// Note that the `yash-builtin` crate also implements the `source` built-in and
111/// registers it with [`Type::Special`], but `source` is excluded from this list
112/// because it is not part of the POSIX-mandated set.
113pub const POSIX_SPECIAL_BUILTIN_NAMES: &[&str] = &[
114 ".", ":", "break", "continue", "eval", "exec", "exit", "export", "readonly", "return", "set",
115 "shift", "times", "trap", "unset",
116];
117
118/// Tests whether `name` is the name of a special built-in utility defined by
119/// POSIX.
120///
121/// See [`POSIX_SPECIAL_BUILTIN_NAMES`] for the list of names this function
122/// checks against.
123#[must_use]
124pub fn is_posix_special_builtin_name(name: &str) -> bool {
125 POSIX_SPECIAL_BUILTIN_NAMES.contains(&name)
126}
127
128/// Result of built-in utility execution
129///
130/// The result type contains an exit status and optional flags that may affect
131/// the behavior of the shell following the built-in execution.
132#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
133#[must_use]
134pub struct Result {
135 exit_status: ExitStatus,
136 divert: crate::semantics::Result,
137 should_retain_redirs: bool,
138}
139
140impl Result {
141 /// Creates a new result.
142 pub const fn new(exit_status: ExitStatus) -> Self {
143 Self {
144 exit_status,
145 divert: crate::semantics::Result::Continue(()),
146 should_retain_redirs: false,
147 }
148 }
149
150 /// Creates a new result with a [`Divert`].
151 #[inline]
152 pub const fn with_exit_status_and_divert(
153 exit_status: ExitStatus,
154 divert: crate::semantics::Result,
155 ) -> Self {
156 Self {
157 exit_status,
158 divert,
159 should_retain_redirs: false,
160 }
161 }
162
163 /// Returns the exit status of this result.
164 ///
165 /// The return value is the argument to the previous invocation of
166 /// [`new`](Self::new) or [`set_exit_status`](Self::set_exit_status).
167 #[inline]
168 #[must_use]
169 pub const fn exit_status(&self) -> ExitStatus {
170 self.exit_status
171 }
172
173 /// Sets the exit status of this result.
174 ///
175 /// See [`exit_status`](Self::exit_status()).
176 #[inline]
177 pub fn set_exit_status(&mut self, exit_status: ExitStatus) {
178 self.exit_status = exit_status
179 }
180
181 /// Returns an optional [`Divert`] to be taken.
182 ///
183 /// The return value is the argument to the previous invocation of
184 /// [`set_divert`](Self::set_divert). The default is `Continue(())`.
185 #[inline]
186 pub const fn divert(&self) -> crate::semantics::Result {
187 self.divert
188 }
189
190 /// Sets a [`Divert`].
191 ///
192 /// See [`divert`](Self::divert()).
193 #[inline]
194 pub fn set_divert(&mut self, divert: crate::semantics::Result) {
195 self.divert = divert;
196 }
197
198 /// Tests whether the caller should retain redirections.
199 ///
200 /// Usually, the shell reverts redirections applied to a built-in after
201 /// executing it. However, redirections applied to a successful `exec`
202 /// built-in should persist. To make it happen, the `exec` built-in calls
203 /// [`retain_redirs`](Self::retain_redirs), and this function returns true.
204 /// In that case, the caller of the built-in should take appropriate actions
205 /// to preserve the effect of the redirections.
206 #[inline]
207 pub const fn should_retain_redirs(&self) -> bool {
208 self.should_retain_redirs
209 }
210
211 /// Flags that redirections applied to the built-in should persist.
212 ///
213 /// Calling this function makes
214 /// [`should_retain_redirs`](Self::should_retain_redirs) return true.
215 /// [`clear_redirs`](Self::clear_redirs) cancels the effect of this
216 /// function.
217 #[inline]
218 pub fn retain_redirs(&mut self) {
219 self.should_retain_redirs = true;
220 }
221
222 /// Cancels the effect of [`retain_redirs`](Self::retain_redirs).
223 #[inline]
224 pub fn clear_redirs(&mut self) {
225 self.should_retain_redirs = false;
226 }
227
228 /// Merges two results by taking the maximum of each field.
229 pub fn max(self, other: Self) -> Self {
230 use std::ops::ControlFlow::{Break, Continue};
231 let divert = match (self.divert, other.divert) {
232 (Continue(()), other) => other,
233 (other, Continue(())) => other,
234 (Break(left), Break(right)) => Break(left.max(right)),
235 };
236
237 Self {
238 exit_status: self.exit_status.max(other.exit_status),
239 divert,
240 should_retain_redirs: self.should_retain_redirs.max(other.should_retain_redirs),
241 }
242 }
243}
244
245impl Default for Result {
246 #[inline]
247 fn default() -> Self {
248 Self::new(ExitStatus::default())
249 }
250}
251
252impl From<ExitStatus> for Result {
253 #[inline]
254 fn from(exit_status: ExitStatus) -> Self {
255 Self::new(exit_status)
256 }
257}
258
259/// Type of functions that implement the behavior of a built-in
260///
261/// The function takes two arguments.
262/// The first is an environment in which the built-in is executed.
263/// The second is arguments to the built-in
264/// (not including the leading command name word).
265pub type Main<S> = fn(&mut Env<S>, Vec<Field>) -> Pin<Box<dyn Future<Output = Result> + '_>>;
266
267/// Built-in utility definition
268///
269/// # Notes on equality
270///
271/// Although this type implements `PartialEq`, comparison between instances of
272/// this type may not always yield predictable results due to the presence of
273/// function pointers. As a result, it is recommended to avoid relying on
274/// equality comparisons for values of this type. See
275/// <https://doc.rust-lang.org/std/ptr/fn.fn_addr_eq.html> for the
276/// characteristics of function pointer comparisons.
277#[allow(
278 unpredictable_function_pointer_comparisons,
279 reason = "we implement PartialEq for this type for historical reasons"
280)]
281#[non_exhaustive]
282pub struct Builtin<S> {
283 /// Type of the built-in
284 pub r#type: Type,
285
286 /// Function that implements the behavior of the built-in
287 pub execute: Main<S>,
288
289 /// Whether the built-in is a declaration utility
290 ///
291 /// The [`decl_util::Glossary`](crate::decl_util::Glossary) implementation
292 /// for [`Env`] uses this field to determine whether a command name is a
293 /// declaration utility. See the [method description] for the value this
294 /// field should have.
295 ///
296 /// [method description]: crate::decl_util::Glossary::is_declaration_utility
297 pub is_declaration_utility: Option<bool>,
298
299 /// Whether the built-in handles signals internally
300 ///
301 /// If `true`, the built-in is responsible for all signal handling during
302 /// its execution, including checking for caught signals and returning
303 /// `Break(Divert::Interrupt(...))` if appropriate. The caller must not
304 /// perform any additional signal checks while the built-in is executing.
305 ///
306 /// If `false` (the default), the built-in does not check for signals at
307 /// all. The caller then checks for a caught SIGINT concurrently with the
308 /// built-in's execution and interrupts the shell if needed. This allows the
309 /// shell to respond to SIGINT even for built-ins that never look at signals
310 /// themselves.
311 ///
312 /// Set this field to `true` for built-ins that handle signals themselves
313 /// (like `fg`, `wait`, `eval`, and `source`), to prevent double-processing.
314 pub handles_signals_internally: bool,
315}
316
317// Not derived automatically because S may not implement Clone or Copy.
318impl<S> Clone for Builtin<S> {
319 fn clone(&self) -> Self {
320 *self
321 }
322}
323
324impl<S> Copy for Builtin<S> {}
325
326impl<S> Debug for Builtin<S> {
327 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
328 f.debug_struct("Builtin")
329 .field("type", &self.r#type)
330 .field("execute", &self.execute)
331 .field("is_declaration_utility", &self.is_declaration_utility)
332 .field(
333 "handles_signals_internally",
334 &self.handles_signals_internally,
335 )
336 .finish()
337 }
338}
339
340// Not derived automatically because S may not implement PartialEq, Eq, or Hash.
341impl<S> PartialEq for Builtin<S> {
342 fn eq(&self, other: &Self) -> bool {
343 self.r#type == other.r#type
344 && std::ptr::fn_addr_eq(self.execute, other.execute)
345 && self.is_declaration_utility == other.is_declaration_utility
346 && self.handles_signals_internally == other.handles_signals_internally
347 }
348}
349
350impl<S> Eq for Builtin<S> {}
351
352impl<S> std::hash::Hash for Builtin<S> {
353 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
354 self.r#type.hash(state);
355 self.execute.hash(state);
356 self.is_declaration_utility.hash(state);
357 self.handles_signals_internally.hash(state);
358 }
359}
360
361impl<S> Builtin<S> {
362 /// Creates a new built-in utility definition.
363 ///
364 /// The `type` and `execute` fields are set to the given arguments.
365 /// The `is_declaration_utility` field is set to `Some(false)`, indicating
366 /// that the built-in is not a declaration utility. The
367 /// `handles_signals_internally` field is set to `false`, meaning that
368 /// the built-in does not handle signals internally by default.
369 pub const fn new(r#type: Type, execute: Main<S>) -> Self {
370 Self {
371 r#type,
372 execute,
373 is_declaration_utility: Some(false),
374 handles_signals_internally: false,
375 }
376 }
377}
378
379#[cfg(test)]
380mod tests {
381 use super::*;
382
383 #[test]
384 fn special_builtin_names_are_sorted() {
385 assert!(POSIX_SPECIAL_BUILTIN_NAMES.is_sorted());
386 }
387
388 #[test]
389 fn is_posix_special_builtin_name_accepts_special_builtins() {
390 assert!(is_posix_special_builtin_name("break"));
391 assert!(is_posix_special_builtin_name(":"));
392 assert!(is_posix_special_builtin_name("."));
393 }
394
395 #[test]
396 fn is_posix_special_builtin_name_rejects_non_special_names() {
397 assert!(!is_posix_special_builtin_name("source"));
398 assert!(!is_posix_special_builtin_name("cd"));
399 assert!(!is_posix_special_builtin_name("foo"));
400 }
401}