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/// Result of built-in utility execution
105///
106/// The result type contains an exit status and optional flags that may affect
107/// the behavior of the shell following the built-in execution.
108#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
109#[must_use]
110pub struct Result {
111 exit_status: ExitStatus,
112 divert: crate::semantics::Result,
113 should_retain_redirs: bool,
114}
115
116impl Result {
117 /// Creates a new result.
118 pub const fn new(exit_status: ExitStatus) -> Self {
119 Self {
120 exit_status,
121 divert: crate::semantics::Result::Continue(()),
122 should_retain_redirs: false,
123 }
124 }
125
126 /// Creates a new result with a [`Divert`].
127 #[inline]
128 pub const fn with_exit_status_and_divert(
129 exit_status: ExitStatus,
130 divert: crate::semantics::Result,
131 ) -> Self {
132 Self {
133 exit_status,
134 divert,
135 should_retain_redirs: false,
136 }
137 }
138
139 /// Returns the exit status of this result.
140 ///
141 /// The return value is the argument to the previous invocation of
142 /// [`new`](Self::new) or [`set_exit_status`](Self::set_exit_status).
143 #[inline]
144 #[must_use]
145 pub const fn exit_status(&self) -> ExitStatus {
146 self.exit_status
147 }
148
149 /// Sets the exit status of this result.
150 ///
151 /// See [`exit_status`](Self::exit_status()).
152 #[inline]
153 pub fn set_exit_status(&mut self, exit_status: ExitStatus) {
154 self.exit_status = exit_status
155 }
156
157 /// Returns an optional [`Divert`] to be taken.
158 ///
159 /// The return value is the argument to the previous invocation of
160 /// [`set_divert`](Self::set_divert). The default is `Continue(())`.
161 #[inline]
162 pub const fn divert(&self) -> crate::semantics::Result {
163 self.divert
164 }
165
166 /// Sets a [`Divert`].
167 ///
168 /// See [`divert`](Self::divert()).
169 #[inline]
170 pub fn set_divert(&mut self, divert: crate::semantics::Result) {
171 self.divert = divert;
172 }
173
174 /// Tests whether the caller should retain redirections.
175 ///
176 /// Usually, the shell reverts redirections applied to a built-in after
177 /// executing it. However, redirections applied to a successful `exec`
178 /// built-in should persist. To make it happen, the `exec` built-in calls
179 /// [`retain_redirs`](Self::retain_redirs), and this function returns true.
180 /// In that case, the caller of the built-in should take appropriate actions
181 /// to preserve the effect of the redirections.
182 #[inline]
183 pub const fn should_retain_redirs(&self) -> bool {
184 self.should_retain_redirs
185 }
186
187 /// Flags that redirections applied to the built-in should persist.
188 ///
189 /// Calling this function makes
190 /// [`should_retain_redirs`](Self::should_retain_redirs) return true.
191 /// [`clear_redirs`](Self::clear_redirs) cancels the effect of this
192 /// function.
193 #[inline]
194 pub fn retain_redirs(&mut self) {
195 self.should_retain_redirs = true;
196 }
197
198 /// Cancels the effect of [`retain_redirs`](Self::retain_redirs).
199 #[inline]
200 pub fn clear_redirs(&mut self) {
201 self.should_retain_redirs = false;
202 }
203
204 /// Merges two results by taking the maximum of each field.
205 pub fn max(self, other: Self) -> Self {
206 use std::ops::ControlFlow::{Break, Continue};
207 let divert = match (self.divert, other.divert) {
208 (Continue(()), other) => other,
209 (other, Continue(())) => other,
210 (Break(left), Break(right)) => Break(left.max(right)),
211 };
212
213 Self {
214 exit_status: self.exit_status.max(other.exit_status),
215 divert,
216 should_retain_redirs: self.should_retain_redirs.max(other.should_retain_redirs),
217 }
218 }
219}
220
221impl Default for Result {
222 #[inline]
223 fn default() -> Self {
224 Self::new(ExitStatus::default())
225 }
226}
227
228impl From<ExitStatus> for Result {
229 #[inline]
230 fn from(exit_status: ExitStatus) -> Self {
231 Self::new(exit_status)
232 }
233}
234
235/// Type of functions that implement the behavior of a built-in
236///
237/// The function takes two arguments.
238/// The first is an environment in which the built-in is executed.
239/// The second is arguments to the built-in
240/// (not including the leading command name word).
241pub type Main<S> = fn(&mut Env<S>, Vec<Field>) -> Pin<Box<dyn Future<Output = Result> + '_>>;
242
243/// Built-in utility definition
244///
245/// # Notes on equality
246///
247/// Although this type implements `PartialEq`, comparison between instances of
248/// this type may not always yield predictable results due to the presence of
249/// function pointers. As a result, it is recommended to avoid relying on
250/// equality comparisons for values of this type. See
251/// <https://doc.rust-lang.org/std/ptr/fn.fn_addr_eq.html> for the
252/// characteristics of function pointer comparisons.
253#[allow(
254 unpredictable_function_pointer_comparisons,
255 reason = "we implement PartialEq for this type for historical reasons"
256)]
257#[non_exhaustive]
258pub struct Builtin<S> {
259 /// Type of the built-in
260 pub r#type: Type,
261
262 /// Function that implements the behavior of the built-in
263 pub execute: Main<S>,
264
265 /// Whether the built-in is a declaration utility
266 ///
267 /// The [`decl_util::Glossary`](crate::decl_util::Glossary) implementation
268 /// for [`Env`] uses this field to determine whether a command name is a
269 /// declaration utility. See the [method description] for the value this
270 /// field should have.
271 ///
272 /// [method description]: crate::decl_util::Glossary::is_declaration_utility
273 pub is_declaration_utility: Option<bool>,
274
275 /// Whether the built-in handles signals internally
276 ///
277 /// If `true`, the built-in is responsible for all signal handling during
278 /// its execution, including checking for caught signals and returning
279 /// `Break(Divert::Interrupt(...))` if appropriate. The caller must not
280 /// perform any additional signal checks while the built-in is executing.
281 ///
282 /// If `false` (the default), the built-in does not check for signals at
283 /// all. The caller then checks for a caught SIGINT concurrently with the
284 /// built-in's execution and interrupts the shell if needed. This allows the
285 /// shell to respond to SIGINT even for built-ins that never look at signals
286 /// themselves.
287 ///
288 /// Set this field to `true` for built-ins that handle signals themselves
289 /// (like `fg`, `wait`, `eval`, and `source`), to prevent double-processing.
290 pub handles_signals_internally: bool,
291}
292
293// Not derived automatically because S may not implement Clone or Copy.
294impl<S> Clone for Builtin<S> {
295 fn clone(&self) -> Self {
296 *self
297 }
298}
299
300impl<S> Copy for Builtin<S> {}
301
302impl<S> Debug for Builtin<S> {
303 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
304 f.debug_struct("Builtin")
305 .field("type", &self.r#type)
306 .field("execute", &self.execute)
307 .field("is_declaration_utility", &self.is_declaration_utility)
308 .field(
309 "handles_signals_internally",
310 &self.handles_signals_internally,
311 )
312 .finish()
313 }
314}
315
316// Not derived automatically because S may not implement PartialEq, Eq, or Hash.
317impl<S> PartialEq for Builtin<S> {
318 fn eq(&self, other: &Self) -> bool {
319 self.r#type == other.r#type
320 && std::ptr::fn_addr_eq(self.execute, other.execute)
321 && self.is_declaration_utility == other.is_declaration_utility
322 && self.handles_signals_internally == other.handles_signals_internally
323 }
324}
325
326impl<S> Eq for Builtin<S> {}
327
328impl<S> std::hash::Hash for Builtin<S> {
329 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
330 self.r#type.hash(state);
331 self.execute.hash(state);
332 self.is_declaration_utility.hash(state);
333 self.handles_signals_internally.hash(state);
334 }
335}
336
337impl<S> Builtin<S> {
338 /// Creates a new built-in utility definition.
339 ///
340 /// The `type` and `execute` fields are set to the given arguments.
341 /// The `is_declaration_utility` field is set to `Some(false)`, indicating
342 /// that the built-in is not a declaration utility. The
343 /// `handles_signals_internally` field is set to `false`, meaning that
344 /// the built-in does not handle signals internally by default.
345 pub const fn new(r#type: Type, execute: Main<S>) -> Self {
346 Self {
347 r#type,
348 execute,
349 is_declaration_utility: Some(false),
350 handles_signals_internally: false,
351 }
352 }
353}