Skip to main content

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;
468pub use errno_from_code;
469pub use errno_from_zxio_code;
470pub use error;
471pub use from_status_like_fdio;
472
473pub trait SourceContext<T, E> {
474    /// Similar to `with_context` in [`anyhow::Context`], but adds the source location of the
475    /// caller. This is especially useful when generating a message to attach to an error object as
476    /// the context.
477    ///
478    /// Example:
479    ///
480    ///     let mount_point = system_task
481    ///         .lookup_path_from_root(locked, mount_point)
482    ///         .with_source_context(|| {
483    ///             format!("lookup path from root: {}", String::from_utf8_lossy(mount_point))
484    ///         })?;
485    ///
486    /// Results in a context message like:
487    ///
488    ///     Caused by:
489    ///         1: lookup path from root: ..., some_file.rs:12:34
490    ///
491    #[track_caller]
492    fn with_source_context(self, context: impl FnOnce() -> String) -> anyhow::Result<T>;
493
494    /// Similar to `context` in [`anyhow::Context`], but adds the source location of the caller.
495    /// This is especially useful when generating a message to attach to an error object as the
496    /// context.
497    #[track_caller]
498    fn source_context<C>(self, context: C) -> anyhow::Result<T>
499    where
500        C: Display + Send + Sync + 'static;
501}
502
503/// All result objects that support `with_context` will also support `with_source_context`.
504impl<T, E> SourceContext<T, E> for Result<T, E>
505where
506    Result<T, E>: anyhow::Context<T, E>,
507{
508    fn with_source_context(self, context: impl FnOnce() -> String) -> anyhow::Result<T> {
509        use anyhow::Context;
510        let caller = std::panic::Location::caller();
511        self.with_context(move || format!("{}, {}", context(), caller))
512    }
513
514    fn source_context<C>(self, context: C) -> anyhow::Result<T>
515    where
516        C: Display + Send + Sync + 'static,
517    {
518        use anyhow::Context;
519        let caller = std::panic::Location::caller();
520        self.context(format!("{}, {}", context, caller))
521    }
522}
523
524const MAX_ERRNO: i32 = 4095;
525
526pub fn is_error_return_value(value: i32) -> bool {
527    value < 0 && value >= -MAX_ERRNO
528}
529
530#[cfg(test)]
531mod tests {
532    use super::*;
533    use std::panic::Location;
534
535    #[test]
536    fn basic_errno_formatting() {
537        let location = std::panic::Location::caller();
538        let errno = Errno { code: ENOENT, location, context: None };
539        assert_eq!(errno.to_string(), format!("ENOENT(2) from {}", location));
540    }
541
542    #[test]
543    fn context_errno_formatting() {
544        let location = std::panic::Location::caller();
545        let errno = Errno { code: ENOENT, location, context: Some("TEST CONTEXT".to_string()) };
546        assert_eq!(
547            errno.to_string(),
548            format!("ENOENT(2) from {}, context: TEST CONTEXT", location)
549        );
550    }
551
552    #[test]
553    fn with_source_context() {
554        let errno = Errno { code: ENOENT, location: std::panic::Location::caller(), context: None };
555        let result: Result<(), Errno> = Err(errno);
556        let error = result.with_source_context(|| format!("42")).unwrap_err();
557        let line_after_error = Location::caller();
558        let expected_prefix =
559            format!("42, {}:{}:", line_after_error.file(), line_after_error.line() - 1,);
560        assert!(
561            error.to_string().starts_with(&expected_prefix),
562            "{} must start with {}",
563            error,
564            expected_prefix
565        );
566    }
567
568    #[test]
569    fn source_context() {
570        let errno = Errno { code: ENOENT, location: std::panic::Location::caller(), context: None };
571        let result: Result<(), Errno> = Err(errno);
572        let error = result.source_context("42").unwrap_err();
573        let line_after_error = Location::caller();
574        let expected_prefix =
575            format!("42, {}:{}:", line_after_error.file(), line_after_error.line() - 1,);
576        assert!(
577            error.to_string().starts_with(&expected_prefix),
578            "{} must start with {}",
579            error,
580            expected_prefix
581        );
582    }
583}