Skip to main content

reverie_process/
error.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9use core::fmt;
10
11use serde::Deserialize;
12use serde::Serialize;
13use syscalls::Errno;
14
15/// Context associated with [`Error`]. Useful for knowing which particular part
16/// of [`super::Command::spawn`] failed.
17#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
18#[repr(u32)]
19pub enum Context {
20    /// No context provided.
21    Unknown,
22    /// Setting CPU affinity failed.
23    Affinity,
24    /// The clone syscall failed.
25    Clone,
26    /// Setting up the tty failed.
27    Tty,
28    /// Setting up stdio failed.
29    Stdio,
30    /// Resetting signals failed.
31    ResetSignals,
32    /// Changing `/proc/{pid}/uid_map` failed.
33    MapUid,
34    /// Changing `/proc/{pid}/setgroups` or `/proc/{pid}/gid_map` failed.
35    MapGid,
36    /// Setting the hostname failed.
37    Hostname,
38    /// Setting the domainname failed.
39    Domainname,
40    /// Chroot failed.
41    Chroot,
42    /// Chdir failed.
43    Chdir,
44    /// Mounting failed.
45    Mount,
46    /// Network configuration failed.
47    Network,
48    /// The pre_exec callback(s) failed.
49    PreExec,
50    /// Setting the seccomp filter failed.
51    Seccomp,
52    /// Exec failed.
53    Exec,
54}
55
56impl Context {
57    /// Returns a string representation of the context.
58    pub fn as_str(&self) -> &'static str {
59        match self {
60            Self::Unknown => "Unknown failure",
61            Self::Affinity => "setting cpu affinity failed",
62            Self::Clone => "clone failed",
63            Self::Tty => "Setting the controlling tty failed",
64            Self::Stdio => "Setting up stdio file descriptors failed",
65            Self::ResetSignals => "Reseting signal handlers failed",
66            Self::MapUid => "Setting UID map failed",
67            Self::MapGid => "Setting GID map failed",
68            Self::Hostname => "Setting hostname failed",
69            Self::Domainname => "Setting domainname failed",
70            Self::Chroot => "chroot failed",
71            Self::Chdir => "chdir failed",
72            Self::Mount => "mount failed",
73            Self::Network => "network configuration failed",
74            Self::PreExec => "pre_exec callback(s) failed",
75            Self::Seccomp => "failed to install seccomp filter",
76            Self::Exec => "execvp failed",
77        }
78    }
79}
80
81impl fmt::Display for Context {
82    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
83        fmt::Write::write_str(f, self.as_str())
84    }
85}
86
87/// An error from spawning a process. This is a thin wrapper around
88/// [`crate::Errno`], but with more context about what went wrong.
89#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
90pub struct Error {
91    errno: Errno,
92    context: Context,
93}
94
95impl Error {
96    /// Creates a new `Error`.
97    pub fn new(errno: Errno, context: Context) -> Self {
98        Self { errno, context }
99    }
100
101    /// Converts a value `S` into an `Error`. Useful for turning `libc` function
102    /// return types into a `Result`.
103    pub fn result<S>(value: S, context: Context) -> Result<S, Self>
104    where
105        S: syscalls::ErrnoSentinel + PartialEq<S>,
106    {
107        Errno::result(value).map_err(|err| Self::new(err, context))
108    }
109
110    /// Gets the errno.
111    pub fn errno(&self) -> Errno {
112        self.errno
113    }
114
115    /// Gets the error context.
116    pub fn context(&self) -> Context {
117        self.context
118    }
119}
120
121impl fmt::Display for Error {
122    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
123        writeln!(f, "{}: {}", self.context, self.errno)
124    }
125}
126
127impl std::error::Error for Error {}
128
129impl From<Errno> for Error {
130    fn from(err: Errno) -> Self {
131        Self::new(err, Context::Unknown)
132    }
133}
134
135impl From<Error> for Errno {
136    fn from(err: Error) -> Errno {
137        err.errno
138    }
139}
140
141impl From<Error> for std::io::Error {
142    fn from(err: Error) -> Self {
143        std::io::Error::from(err.errno)
144    }
145}
146
147impl From<[u8; 8]> for Error {
148    /// Deserializes an `Error` from bytes. Useful for receiving the error
149    /// through a pipe from the child process.
150    fn from(bytes: [u8; 8]) -> Self {
151        debug_assert_eq!(core::mem::size_of::<Self>(), 8);
152        unsafe { core::mem::transmute(bytes) }
153    }
154}
155
156impl From<Error> for [u8; 8] {
157    /// Serializes an `Error` into bytes. Useful for sending the error through a
158    /// pipe to the parent process.
159    fn from(error: Error) -> Self {
160        debug_assert_eq!(core::mem::size_of::<Self>(), 8);
161        unsafe { core::mem::transmute(error) }
162    }
163}
164
165pub(super) trait AddContext<T> {
166    fn context(self, context: Context) -> Result<T, Error>;
167}
168
169impl<T> AddContext<T> for Result<T, Errno> {
170    fn context(self, context: Context) -> Result<T, Error> {
171        self.map_err(move |errno| Error::new(errno, context))
172    }
173}
174
175impl<T> AddContext<T> for Result<T, nix::errno::Errno> {
176    fn context(self, context: Context) -> Result<T, Error> {
177        self.map_err(move |errno| Error::new(Errno::new(errno as i32), context))
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184
185    #[test]
186    fn to_bytes() {
187        let bytes: [u8; 8] = Error::new(Errno::ENOENT, Context::Exec).into();
188        assert_eq!(Error::from(bytes), Error::new(Errno::ENOENT, Context::Exec));
189    }
190}