conch_runtime_pshaw/
error.rs

1//! A module defining the various kinds of errors that may arise
2//! while executing commands.
3#![allow(unused_qualifications)] // False positives with thiserror derive
4
5use crate::io::Permissions;
6use crate::Fd;
7use std::convert::From;
8use std::error::Error;
9use std::fmt::{self, Display, Formatter};
10use std::io::Error as IoError;
11
12/// Determines whether an error should be treated as "fatal".
13///
14/// Typically, "fatal" errors will abort any currently running commands
15/// (e.g. loops, compound commands, pipelines, etc.) all the way
16/// to a top level command, and consider it unsuccessful. On the other hand,
17/// non-fatal errors are usually swallowed by intermediate commands, and the
18/// execution is allowed to continue.
19///
20/// Ultimately it is up to the caller to decide how to handle fatal vs non-fatal
21/// errors.
22pub trait IsFatalError: 'static + Send + Sync + Error {
23    /// Checks whether the error should be considered a "fatal" error.
24    fn is_fatal(&self) -> bool;
25}
26
27impl IsFatalError for void::Void {
28    fn is_fatal(&self) -> bool {
29        void::unreachable(*self)
30    }
31}
32
33/// An error which may arise during parameter expansion.
34#[derive(PartialEq, Eq, Clone, Debug, thiserror::Error)]
35pub enum ExpansionError {
36    /// Attempted to divide by zero in an arithmetic subsitution.
37    #[error("attempted to divide by zero")]
38    DivideByZero,
39    /// Attempted to raise to a negative power in an arithmetic subsitution.
40    #[error("attempted to raise to a negative power")]
41    NegativeExponent,
42    /// Attempted to assign a special parameter, e.g. `${!:=value}`.
43    #[error("{0}: cannot assign in this way")]
44    BadAssig(String),
45    /// Attempted to evaluate a null or unset parameter, i.e. `${var:?msg}`.
46    #[error("{0}: {1}")]
47    EmptyParameter(String /* var */, String /* msg */),
48}
49
50impl IsFatalError for ExpansionError {
51    fn is_fatal(&self) -> bool {
52        // According to POSIX expansion errors should always be considered fatal
53        match *self {
54            ExpansionError::DivideByZero
55            | ExpansionError::NegativeExponent
56            | ExpansionError::BadAssig(_)
57            | ExpansionError::EmptyParameter(_, _) => true,
58        }
59    }
60}
61
62/// An error which may arise during redirection.
63#[derive(Debug, thiserror::Error)]
64pub enum RedirectionError {
65    /// A redirect path evaluated to multiple fields.
66    Ambiguous(Vec<String>),
67    /// Attempted to duplicate an invalid file descriptor.
68    BadFdSrc(String),
69    /// Attempted to duplicate a file descriptor with Read/Write
70    /// access that differs from the original.
71    BadFdPerms(Fd, Permissions /* new perms */),
72    /// Any I/O error returned by the OS during execution and the
73    /// file that caused the error if applicable.
74    Io(#[source] IoError, Option<String>),
75}
76
77impl Eq for RedirectionError {}
78impl PartialEq for RedirectionError {
79    fn eq(&self, other: &Self) -> bool {
80        use self::RedirectionError::*;
81
82        match (self, other) {
83            (&Io(ref e1, ref a), &Io(ref e2, ref b)) => e1.kind() == e2.kind() && a == b,
84            (&Ambiguous(ref a), &Ambiguous(ref b)) => a == b,
85            (&BadFdSrc(ref a), &BadFdSrc(ref b)) => a == b,
86            (&BadFdPerms(fd_a, perms_a), &BadFdPerms(fd_b, perms_b)) => {
87                fd_a == fd_b && perms_a == perms_b
88            }
89            _ => false,
90        }
91    }
92}
93
94impl Display for RedirectionError {
95    fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
96        match *self {
97            RedirectionError::Ambiguous(ref v) => {
98                write!(fmt, "a redirect path evaluated to multiple fields: ")?;
99                let mut iter = v.iter();
100                if let Some(s) = iter.next() {
101                    write!(fmt, "{}", s)?;
102                }
103                for s in iter {
104                    write!(fmt, " {}", s)?;
105                }
106                Ok(())
107            }
108
109            RedirectionError::BadFdSrc(ref fd) => {
110                let description = "attempted to duplicate an invalid file descriptor";
111                write!(fmt, "{}: {}", description, fd)
112            }
113
114            RedirectionError::BadFdPerms(fd, perms) => {
115                let description = "attmpted to duplicate a file descritpor with Read/Write access that differs from the original";
116                write!(
117                    fmt,
118                    "{}: {}, desired permissions: {}",
119                    description, fd, perms
120                )
121            }
122
123            RedirectionError::Io(ref e, None) => write!(fmt, "{}", e),
124            RedirectionError::Io(ref e, Some(ref path)) => write!(fmt, "{}: {}", e, path),
125        }
126    }
127}
128
129impl IsFatalError for RedirectionError {
130    fn is_fatal(&self) -> bool {
131        match *self {
132            RedirectionError::Ambiguous(_)
133            | RedirectionError::BadFdSrc(_)
134            | RedirectionError::BadFdPerms(_, _)
135            | RedirectionError::Io(_, _) => false,
136        }
137    }
138}
139
140/// An error which may arise when spawning a command process.
141#[derive(Debug, thiserror::Error)]
142pub enum CommandError {
143    /// Unable to find a command/function/builtin to execute.
144    NotFound(String),
145    /// Utility or script does not have executable permissions.
146    NotExecutable(String),
147    /// Any I/O error returned by the OS during execution and the
148    /// file that caused the error if applicable.
149    Io(#[source] IoError, Option<String>),
150}
151
152impl Eq for CommandError {}
153impl PartialEq for CommandError {
154    fn eq(&self, other: &Self) -> bool {
155        use self::CommandError::*;
156
157        match (self, other) {
158            (&NotFound(ref a), &NotFound(ref b))
159            | (&NotExecutable(ref a), &NotExecutable(ref b)) => a == b,
160            (&Io(ref e1, ref a), &Io(ref e2, ref b)) => e1.kind() == e2.kind() && a == b,
161            _ => false,
162        }
163    }
164}
165
166impl Display for CommandError {
167    fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
168        match *self {
169            CommandError::NotFound(ref c) => write!(fmt, "{}: command not found", c),
170            CommandError::NotExecutable(ref c) => write!(fmt, "{}: command not executable", c),
171            CommandError::Io(ref e, None) => write!(fmt, "{}", e),
172            CommandError::Io(ref e, Some(ref path)) => write!(fmt, "{}: {}", e, path),
173        }
174    }
175}
176
177impl IsFatalError for CommandError {
178    fn is_fatal(&self) -> bool {
179        match *self {
180            CommandError::NotFound(_) | CommandError::NotExecutable(_) | CommandError::Io(_, _) => {
181                false
182            }
183        }
184    }
185}
186
187/// An error which may arise while executing commands.
188#[derive(Debug, thiserror::Error)]
189pub enum RuntimeError {
190    /// Any I/O error returned by the OS during execution and the
191    /// file that caused the error if applicable.
192    Io(#[source] IoError, Option<String>),
193    /// Any error that occured during a parameter expansion.
194    Expansion(#[from] ExpansionError),
195    /// Any error that occured during a redirection.
196    Redirection(#[from] RedirectionError),
197    /// Any error that occured during a command spawning.
198    Command(#[from] CommandError),
199    /// Runtime feature not currently supported.
200    Unimplemented(&'static str),
201}
202
203impl Eq for RuntimeError {}
204impl PartialEq for RuntimeError {
205    fn eq(&self, other: &Self) -> bool {
206        use self::RuntimeError::*;
207
208        match (self, other) {
209            (&Io(ref e1, ref a), &Io(ref e2, ref b)) => e1.kind() == e2.kind() && a == b,
210            (&Expansion(ref a), &Expansion(ref b)) => a == b,
211            (&Redirection(ref a), &Redirection(ref b)) => a == b,
212            (&Command(ref a), &Command(ref b)) => a == b,
213            (&Unimplemented(a), &Unimplemented(b)) => a == b,
214            _ => false,
215        }
216    }
217}
218
219impl Display for RuntimeError {
220    fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result {
221        match *self {
222            RuntimeError::Expansion(ref e) => write!(fmt, "{}", e),
223            RuntimeError::Redirection(ref e) => write!(fmt, "{}", e),
224            RuntimeError::Command(ref e) => write!(fmt, "{}", e),
225            RuntimeError::Unimplemented(e) => write!(fmt, "{}", e),
226            RuntimeError::Io(ref e, None) => write!(fmt, "{}", e),
227            RuntimeError::Io(ref e, Some(ref path)) => write!(fmt, "{}: {}", e, path),
228        }
229    }
230}
231
232impl IsFatalError for RuntimeError {
233    fn is_fatal(&self) -> bool {
234        match *self {
235            RuntimeError::Expansion(ref e) => e.is_fatal(),
236            RuntimeError::Redirection(ref e) => e.is_fatal(),
237            RuntimeError::Command(ref e) => e.is_fatal(),
238            RuntimeError::Io(_, _) | RuntimeError::Unimplemented(_) => false,
239        }
240    }
241}
242
243impl From<IoError> for RuntimeError {
244    fn from(err: IoError) -> Self {
245        RuntimeError::Io(err, None)
246    }
247}
248
249impl From<void::Void> for RuntimeError {
250    fn from(err: void::Void) -> Self {
251        void::unreachable(err)
252    }
253}
254
255#[cfg(test)]
256mod tests {
257    use super::*;
258
259    #[test]
260    fn ensure_runtime_errors_are_send_and_sync() {
261        fn send_and_sync<T: Send + Sync>() {}
262
263        send_and_sync::<ExpansionError>();
264        send_and_sync::<RedirectionError>();
265        send_and_sync::<CommandError>();
266        send_and_sync::<RuntimeError>();
267    }
268}