starnix_uapi/
errors.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#![allow(dead_code)]
6
7use crate::{SA_RESTART, sigaction_t};
8use std::fmt::{Debug, Display, Formatter};
9
10#[derive(Clone, Debug)]
11pub struct Errno {
12    pub code: ErrnoCode,
13    location: &'static std::panic::Location<'static>,
14    context: Option<String>,
15}
16
17impl Errno {
18    /// Creates a new `Errno` with the given `ErrnoCode` and the location of the caller.
19    ///
20    /// Callers should use the `errno!` macro instead of calling this function directly.
21    #[track_caller]
22    pub fn new(code: ErrnoCode) -> Self {
23        Errno { code, location: std::panic::Location::caller(), context: None }
24    }
25
26    #[track_caller]
27    pub fn with_context(code: ErrnoCode, context: impl ToString) -> Self {
28        Errno { code, location: std::panic::Location::caller(), context: Some(context.to_string()) }
29    }
30
31    pub fn return_value(&self) -> u64 {
32        self.code.return_value()
33    }
34
35    /// Returns whether this `Errno` indicates that a restartable syscall was interrupted.
36    pub fn is_restartable(&self) -> bool {
37        self.code.is_restartable()
38    }
39}
40
41impl PartialEq for Errno {
42    fn eq(&self, other: &Self) -> bool {
43        self.code == other.code
44    }
45}
46
47impl PartialEq<ErrnoCode> for Errno {
48    fn eq(&self, other: &ErrnoCode) -> bool {
49        self.code == *other
50    }
51}
52
53impl Eq for Errno {}
54
55impl Display for Errno {
56    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
57        if let Some(context) = &self.context {
58            write!(f, "{} from {}, context: {}", self.code, self.location, context)
59        } else {
60            write!(f, "{} from {}", self.code, self.location)
61        }
62    }
63}
64
65impl std::error::Error for Errno {}
66
67impl From<Errno> for zx_status::Status {
68    fn from(e: Errno) -> Self {
69        match e.code.error_code() {
70            crate::uapi::ENOENT => zx_status::Status::NOT_FOUND,
71            crate::uapi::ENOMEM => zx_status::Status::NO_MEMORY,
72            crate::uapi::EINVAL => zx_status::Status::INVALID_ARGS,
73            crate::uapi::ETIMEDOUT => zx_status::Status::TIMED_OUT,
74            crate::uapi::EBUSY => zx_status::Status::UNAVAILABLE,
75            crate::uapi::EEXIST => zx_status::Status::ALREADY_EXISTS,
76            crate::uapi::EPIPE => zx_status::Status::PEER_CLOSED,
77            crate::uapi::ENAMETOOLONG => zx_status::Status::BAD_PATH,
78            crate::uapi::EIO => zx_status::Status::IO,
79            crate::uapi::EISDIR => zx_status::Status::NOT_FILE,
80            crate::uapi::ENOTDIR => zx_status::Status::NOT_DIR,
81            crate::uapi::EOPNOTSUPP => zx_status::Status::NOT_SUPPORTED,
82            crate::uapi::EBADF => zx_status::Status::BAD_HANDLE,
83            crate::uapi::EACCES => zx_status::Status::ACCESS_DENIED,
84            crate::uapi::EAGAIN => zx_status::Status::SHOULD_WAIT,
85            crate::uapi::EFBIG => zx_status::Status::FILE_BIG,
86            crate::uapi::ENOSPC => zx_status::Status::NO_SPACE,
87            crate::uapi::ENOTEMPTY => zx_status::Status::NOT_EMPTY,
88            crate::uapi::EPROTONOSUPPORT => zx_status::Status::PROTOCOL_NOT_SUPPORTED,
89            crate::uapi::ENETUNREACH => zx_status::Status::ADDRESS_UNREACHABLE,
90            crate::uapi::EADDRINUSE => zx_status::Status::ADDRESS_IN_USE,
91            crate::uapi::ENOTCONN => zx_status::Status::NOT_CONNECTED,
92            crate::uapi::ECONNREFUSED => zx_status::Status::CONNECTION_REFUSED,
93            crate::uapi::ECONNRESET => zx_status::Status::CONNECTION_RESET,
94            crate::uapi::ECONNABORTED => zx_status::Status::CONNECTION_ABORTED,
95            _ => zx_status::Status::NOT_SUPPORTED,
96        }
97    }
98}
99
100#[derive(Eq, PartialEq, Copy, Clone)]
101pub struct ErrnoCode(u32);
102
103impl ErrnoCode {
104    pub const fn from_return_value(retval: u64) -> Self {
105        let retval = retval as i64;
106        if retval >= 0 {
107            // Collapse all success codes to 0. This is the only value in the u32 range which
108            // is guaranteed to not be an error code.
109            return Self(0);
110        }
111        Self(-retval as u32)
112    }
113
114    pub const fn from_error_code(code: i16) -> Self {
115        Self(code as u32)
116    }
117
118    pub const fn return_value(&self) -> u64 {
119        -(self.0 as i32) as u64
120    }
121
122    pub const fn error_code(&self) -> u32 {
123        self.0
124    }
125
126    /// Returns whether this `ErrnoCode` indicates that a restartable syscall was interrupted.
127    pub fn is_restartable(&self) -> bool {
128        matches!(*self, ERESTARTSYS | ERESTARTNOINTR | ERESTARTNOHAND | ERESTART_RESTARTBLOCK)
129    }
130
131    /// Returns whether a combination of this `ErrnoCode` and a given `sigaction_t` mean that an
132    /// interrupted syscall should be restarted.
133    ///
134    /// Conditions for restarting syscalls:
135    ///
136    /// * the error code is `ERESTARTSYS`, `ERESTARTNOINTR`, `ERESTARTNOHAND`, or
137    ///   `ERESTART_RESTARTBLOCK` and
138    /// * `ERESTARTNOINTR` is always restarted
139    /// * `ERESTARTSYS` is only restarted if the `SA_RESTART` flag is set in the `sigaction_t`
140    /// * all four error codes are restarted if no signal handler was present in the `sigaction_t`
141    pub fn should_restart(&self, sigaction: Option<sigaction_t>) -> bool {
142        // If sigaction is None, the syscall must be restarted if it is restartable. The default
143        // sigaction will not have a sighandler, which will guarantee a restart.
144        let sigaction = sigaction.unwrap_or_default();
145
146        let should_restart_even_if_sigaction_handler_not_null = match *self {
147            ERESTARTSYS => sigaction.sa_flags & SA_RESTART as u64 != 0,
148            ERESTARTNOINTR => true,
149            ERESTARTNOHAND | ERESTART_RESTARTBLOCK => false,
150
151            // Never restart a syscall unless it's one of the above errno codes.
152            _ => return false,
153        };
154
155        // Always restart if the signal did not call a handler (i.e. SIGSTOP).
156        should_restart_even_if_sigaction_handler_not_null || sigaction.sa_handler.addr == 0
157    }
158}
159
160impl Debug for ErrnoCode {
161    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
162        write!(f, "{}", self)
163    }
164}
165
166impl Display for ErrnoCode {
167    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
168        write!(f, "{}({})", self.name(), self.0)
169    }
170}
171
172// Special errors indicating a blocking syscall was interrupted, but it can be restarted.
173//
174// They are not defined in uapi, but can be observed by ptrace on Linux.
175//
176// If the syscall is restartable, it might not be restarted, depending on the value of SA_RESTART
177// for the signal handler and the specific restartable error code.
178// But it will always be restarted if the signal did not call a userspace signal handler.
179// If not restarted, this error code is converted into EINTR.
180//
181// More information can be found at
182// https://cs.opensource.google/gvisor/gvisor/+/master:pkg/errors/linuxerr/internal.go;l=71;drc=2bb73c7bd7dcf0b36e774d8e82e464d04bc81f4b.
183
184/// Convert to EINTR if interrupted by a signal handler without SA_RESTART enabled, otherwise
185/// restart.
186pub const ERESTARTSYS: ErrnoCode = ErrnoCode(512);
187
188/// Always restart, regardless of the signal handler.
189pub const ERESTARTNOINTR: ErrnoCode = ErrnoCode(513);
190
191/// Convert to EINTR if interrupted by a signal handler. SA_RESTART is ignored. Otherwise restart.
192pub const ERESTARTNOHAND: ErrnoCode = ErrnoCode(514);
193
194/// Like `ERESTARTNOHAND`, but restart by invoking a closure instead of calling the syscall
195/// implementation again.
196pub const ERESTART_RESTARTBLOCK: ErrnoCode = ErrnoCode(516);
197
198/// An extension trait for `Result<T, Errno>`.
199pub trait ErrnoResultExt<T> {
200    /// Maps `Err(EINTR)` to the specified errno.
201    fn map_eintr(self, make_errno: impl Fn() -> Errno) -> Result<T, Errno>;
202}
203
204impl<T> ErrnoResultExt<T> for Result<T, Errno> {
205    fn map_eintr(self, make_errno: impl Fn() -> Errno) -> Result<T, Errno> {
206        self.map_err(|err| if err == EINTR { make_errno() } else { err })
207    }
208}
209
210macro_rules! errno_codes {
211    ($($name:ident),+) => {
212        $(pub const $name: ErrnoCode = ErrnoCode(crate::uapi::$name);)+
213
214        impl ErrnoCode {
215            fn name(&self) -> &'static str {
216                match self.0 {
217                    $(
218                        crate::uapi::$name => stringify!($name),
219                    )+
220                    _ => "unknown error code",
221                }
222            }
223        }
224
225        #[cfg(test)]
226        #[test]
227        fn expected_errno_code_strings() {
228            $(
229                assert_eq!(
230                    $name.to_string(),
231                    format!("{}({})", stringify!($name), crate::uapi::$name),
232                );
233            )+
234        }
235    };
236}
237
238errno_codes![
239    EPERM,
240    ENOENT,
241    ESRCH,
242    EINTR,
243    EIO,
244    ENXIO,
245    E2BIG,
246    ENOEXEC,
247    EBADF,
248    ECHILD,
249    EAGAIN,
250    ENOMEM,
251    EACCES,
252    EFAULT,
253    ENOTBLK,
254    EBUSY,
255    EEXIST,
256    EXDEV,
257    ENODEV,
258    ENOTDIR,
259    EISDIR,
260    EINVAL,
261    ENFILE,
262    EMFILE,
263    ENOTTY,
264    ETXTBSY,
265    EFBIG,
266    ENOSPC,
267    ESPIPE,
268    EROFS,
269    EMLINK,
270    EPIPE,
271    EDOM,
272    ERANGE,
273    ENAMETOOLONG,
274    ENOLCK,
275    ENOSYS,
276    ENOTEMPTY,
277    ELOOP,
278    ENOMSG,
279    EIDRM,
280    ECHRNG,
281    EL2NSYNC,
282    EL3HLT,
283    EL3RST,
284    ELNRNG,
285    EUNATCH,
286    ENOCSI,
287    EL2HLT,
288    EBADE,
289    EBADR,
290    EXFULL,
291    ENOANO,
292    EBADRQC,
293    EBADSLT,
294    EDEADLOCK,
295    EBFONT,
296    ENOSTR,
297    ENODATA,
298    ETIME,
299    ENOSR,
300    ENONET,
301    ENOPKG,
302    EREMOTE,
303    ENOLINK,
304    EADV,
305    ESRMNT,
306    ECOMM,
307    EPROTO,
308    EMULTIHOP,
309    EDOTDOT,
310    EBADMSG,
311    EOVERFLOW,
312    ENOTUNIQ,
313    EBADFD,
314    EREMCHG,
315    ELIBACC,
316    ELIBBAD,
317    ELIBSCN,
318    ELIBMAX,
319    ELIBEXEC,
320    EILSEQ,
321    ERESTART,
322    ESTRPIPE,
323    EUSERS,
324    ENOTSOCK,
325    EDESTADDRREQ,
326    EMSGSIZE,
327    EPROTOTYPE,
328    ENOPROTOOPT,
329    EPROTONOSUPPORT,
330    ESOCKTNOSUPPORT,
331    EOPNOTSUPP,
332    EPFNOSUPPORT,
333    EAFNOSUPPORT,
334    EADDRINUSE,
335    EADDRNOTAVAIL,
336    ENETDOWN,
337    ENETUNREACH,
338    ENETRESET,
339    ECONNABORTED,
340    ECONNRESET,
341    ENOBUFS,
342    EISCONN,
343    ENOTCONN,
344    ESHUTDOWN,
345    ETOOMANYREFS,
346    ETIMEDOUT,
347    ECONNREFUSED,
348    EHOSTDOWN,
349    EHOSTUNREACH,
350    EALREADY,
351    EINPROGRESS,
352    ESTALE,
353    EUCLEAN,
354    ENOTNAM,
355    ENAVAIL,
356    EISNAM,
357    EREMOTEIO,
358    EDQUOT,
359    ENOMEDIUM,
360    EMEDIUMTYPE,
361    ECANCELED,
362    ENOKEY,
363    EKEYEXPIRED,
364    EKEYREVOKED,
365    EKEYREJECTED,
366    EOWNERDEAD,
367    ENOTRECOVERABLE,
368    ERFKILL,
369    EHWPOISON
370];
371
372// ENOTSUP is a different error in posix, but has the same value as EOPNOTSUPP in linux.
373pub const ENOTSUP: ErrnoCode = EOPNOTSUPP;
374
375/// `errno` returns an `Errno` struct tagged with the current file name and line number.
376///
377/// Use `error!` instead if you want the `Errno` to be wrapped in an `Err`.
378#[macro_export]
379macro_rules! errno {
380    ($err:ident) => {
381        $crate::errors::Errno::new($crate::errors::$err)
382    };
383    ($err:ident, $context:expr) => {
384        $crate::errors::Errno::with_context($crate::errors::$err, $context.to_string())
385    };
386}
387
388/// `error` returns a `Err` containing an `Errno` struct tagged with the current file name and line
389/// number.
390///
391/// Use `errno!` instead if you want an unwrapped, but still tagged, `Errno`.
392#[macro_export]
393macro_rules! error {
394    ($($args:tt)*) => { Err($crate::errno!($($args)*)) };
395}
396
397/// `errno_from_code` returns a `Err` containing an `Errno` struct with the given error code and is
398/// tagged with the current file name and line number.
399#[macro_export]
400macro_rules! errno_from_code {
401    ($err:expr) => {{ $crate::errors::Errno::new($crate::errors::ErrnoCode::from_error_code($err)) }};
402}
403
404/// `errno_from_zxio_code` returns an `Errno` struct with the given error code and is
405/// tagged with the current file name and line number.
406#[macro_export]
407macro_rules! errno_from_zxio_code {
408    ($err:expr) => {{ $crate::errno_from_code!($err.raw()) }};
409}
410
411// There isn't really a mapping from zx_status::Status to Errno. The correct mapping is
412// context-specific but this converter is a reasonable first-approximation. The translation matches
413// fdio_status_to_errno. See https://fxbug.dev/42105838 for more context.
414// TODO: Replace clients with more context-specific mappings.
415#[macro_export]
416macro_rules! from_status_like_fdio {
417    ($status:expr) => {{ $crate::from_status_like_fdio!($status, "") }};
418    ($status:expr, $context:expr) => {{
419        match $status {
420            $crate::__zx_status::Status::NOT_FOUND => $crate::errno!(ENOENT, $context),
421            $crate::__zx_status::Status::NO_MEMORY => $crate::errno!(ENOMEM, $context),
422            $crate::__zx_status::Status::INVALID_ARGS => $crate::errno!(EINVAL, $context),
423            $crate::__zx_status::Status::BUFFER_TOO_SMALL => $crate::errno!(EINVAL, $context),
424            $crate::__zx_status::Status::TIMED_OUT => $crate::errno!(ETIMEDOUT, $context),
425            $crate::__zx_status::Status::UNAVAILABLE => $crate::errno!(EBUSY, $context),
426            $crate::__zx_status::Status::ALREADY_EXISTS => $crate::errno!(EEXIST, $context),
427            $crate::__zx_status::Status::PEER_CLOSED => $crate::errno!(EPIPE, $context),
428            $crate::__zx_status::Status::BAD_STATE => $crate::errno!(EPIPE, $context),
429            $crate::__zx_status::Status::BAD_PATH => $crate::errno!(ENAMETOOLONG, $context),
430            $crate::__zx_status::Status::IO => $crate::errno!(EIO, $context),
431            $crate::__zx_status::Status::NOT_FILE => $crate::errno!(EISDIR, $context),
432            $crate::__zx_status::Status::NOT_DIR => $crate::errno!(ENOTDIR, $context),
433            $crate::__zx_status::Status::NOT_SUPPORTED => $crate::errno!(EOPNOTSUPP, $context),
434            $crate::__zx_status::Status::WRONG_TYPE => $crate::errno!(EOPNOTSUPP, $context),
435            $crate::__zx_status::Status::OUT_OF_RANGE => $crate::errno!(EINVAL, $context),
436            $crate::__zx_status::Status::NO_RESOURCES => $crate::errno!(ENOMEM, $context),
437            $crate::__zx_status::Status::BAD_HANDLE => $crate::errno!(EBADF, $context),
438            $crate::__zx_status::Status::ACCESS_DENIED => $crate::errno!(EACCES, $context),
439            $crate::__zx_status::Status::SHOULD_WAIT => $crate::errno!(EAGAIN, $context),
440            $crate::__zx_status::Status::FILE_BIG => $crate::errno!(EFBIG, $context),
441            $crate::__zx_status::Status::NO_SPACE => $crate::errno!(ENOSPC, $context),
442            $crate::__zx_status::Status::NOT_EMPTY => $crate::errno!(ENOTEMPTY, $context),
443            $crate::__zx_status::Status::IO_REFUSED => $crate::errno!(ECONNREFUSED, $context),
444            $crate::__zx_status::Status::IO_INVALID => $crate::errno!(EIO, $context),
445            $crate::__zx_status::Status::CANCELED => $crate::errno!(EBADF, $context),
446            $crate::__zx_status::Status::PROTOCOL_NOT_SUPPORTED => {
447                $crate::errno!(EPROTONOSUPPORT, $context)
448            }
449            $crate::__zx_status::Status::ADDRESS_UNREACHABLE => {
450                $crate::errno!(ENETUNREACH, $context)
451            }
452            $crate::__zx_status::Status::ADDRESS_IN_USE => $crate::errno!(EADDRINUSE, $context),
453            $crate::__zx_status::Status::NOT_CONNECTED => $crate::errno!(ENOTCONN, $context),
454            $crate::__zx_status::Status::CONNECTION_REFUSED => {
455                $crate::errno!(ECONNREFUSED, $context)
456            }
457            $crate::__zx_status::Status::CONNECTION_RESET => $crate::errno!(ECONNRESET, $context),
458            $crate::__zx_status::Status::CONNECTION_ABORTED => {
459                $crate::errno!(ECONNABORTED, $context)
460            }
461            _ => $crate::errno!(EIO, $context),
462        }
463    }};
464}
465
466// Public re-export of macros allows them to be used like regular rust items.
467pub use {errno, errno_from_code, errno_from_zxio_code, error, from_status_like_fdio};
468
469pub trait SourceContext<T, E> {
470    /// Similar to `with_context` in [`anyhow::Context`], but adds the source location of the
471    /// caller. This is especially useful when generating a message to attach to an error object as
472    /// the context.
473    ///
474    /// Example:
475    ///
476    ///     let mount_point = system_task
477    ///         .lookup_path_from_root(locked, mount_point)
478    ///         .with_source_context(|| {
479    ///             format!("lookup path from root: {}", String::from_utf8_lossy(mount_point))
480    ///         })?;
481    ///
482    /// Results in a context message like:
483    ///
484    ///     Caused by:
485    ///         1: lookup path from root: ..., some_file.rs:12:34
486    ///
487    #[track_caller]
488    fn with_source_context(self, context: impl FnOnce() -> String) -> anyhow::Result<T>;
489
490    /// Similar to `context` in [`anyhow::Context`], but adds the source location of the caller.
491    /// This is especially useful when generating a message to attach to an error object as the
492    /// context.
493    #[track_caller]
494    fn source_context<C>(self, context: C) -> anyhow::Result<T>
495    where
496        C: Display + Send + Sync + 'static;
497}
498
499/// All result objects that support `with_context` will also support `with_source_context`.
500impl<T, E> SourceContext<T, E> for Result<T, E>
501where
502    Result<T, E>: anyhow::Context<T, E>,
503{
504    fn with_source_context(self, context: impl FnOnce() -> String) -> anyhow::Result<T> {
505        use anyhow::Context;
506        let caller = std::panic::Location::caller();
507        self.with_context(move || format!("{}, {}", context(), caller))
508    }
509
510    fn source_context<C>(self, context: C) -> anyhow::Result<T>
511    where
512        C: Display + Send + Sync + 'static,
513    {
514        use anyhow::Context;
515        let caller = std::panic::Location::caller();
516        self.context(format!("{}, {}", context, caller))
517    }
518}
519
520#[cfg(test)]
521mod tests {
522    use super::*;
523    use std::panic::Location;
524
525    #[test]
526    fn basic_errno_formatting() {
527        let location = std::panic::Location::caller();
528        let errno = Errno { code: ENOENT, location, context: None };
529        assert_eq!(errno.to_string(), format!("ENOENT(2) from {}", location));
530    }
531
532    #[test]
533    fn context_errno_formatting() {
534        let location = std::panic::Location::caller();
535        let errno = Errno { code: ENOENT, location, context: Some("TEST CONTEXT".to_string()) };
536        assert_eq!(
537            errno.to_string(),
538            format!("ENOENT(2) from {}, context: TEST CONTEXT", location)
539        );
540    }
541
542    #[test]
543    fn with_source_context() {
544        let errno = Errno { code: ENOENT, location: std::panic::Location::caller(), context: None };
545        let result: Result<(), Errno> = Err(errno);
546        let error = result.with_source_context(|| format!("42")).unwrap_err();
547        let line_after_error = Location::caller();
548        let expected_prefix =
549            format!("42, {}:{}:", line_after_error.file(), line_after_error.line() - 1,);
550        assert!(
551            error.to_string().starts_with(&expected_prefix),
552            "{} must start with {}",
553            error,
554            expected_prefix
555        );
556    }
557
558    #[test]
559    fn source_context() {
560        let errno = Errno { code: ENOENT, location: std::panic::Location::caller(), context: None };
561        let result: Result<(), Errno> = Err(errno);
562        let error = result.source_context("42").unwrap_err();
563        let line_after_error = Location::caller();
564        let expected_prefix =
565            format!("42, {}:{}:", line_after_error.file(), line_after_error.line() - 1,);
566        assert!(
567            error.to_string().starts_with(&expected_prefix),
568            "{} must start with {}",
569            error,
570            expected_prefix
571        );
572    }
573}