Skip to main content

libbpf_rs/
error.rs

1use std::borrow::Borrow;
2use std::borrow::Cow;
3use std::error;
4use std::error::Error as _;
5use std::fmt::Debug;
6use std::fmt::Display;
7use std::fmt::Formatter;
8use std::fmt::Result as FmtResult;
9use std::io;
10use std::mem::transmute;
11use std::ops::Deref;
12use std::result;
13
14/// A result type using our [`Error`] by default.
15pub type Result<T, E = Error> = result::Result<T, E>;
16
17#[allow(clippy::wildcard_imports)]
18mod private {
19    use super::*;
20
21    pub trait Sealed {}
22
23    impl<T> Sealed for Option<T> {}
24    impl<T, E> Sealed for Result<T, E> {}
25    impl Sealed for &'static str {}
26    impl Sealed for String {}
27    impl Sealed for Error {}
28
29    impl Sealed for io::Error {}
30}
31
32/// A `str` replacement whose owned representation is a `Box<str>` and
33/// not a `String`.
34#[derive(Debug)]
35#[repr(transparent)]
36#[doc(hidden)]
37pub struct Str(str);
38
39impl ToOwned for Str {
40    type Owned = Box<str>;
41
42    #[inline]
43    fn to_owned(&self) -> Self::Owned {
44        self.0.to_string().into_boxed_str()
45    }
46}
47
48impl Borrow<Str> for Box<str> {
49    #[inline]
50    fn borrow(&self) -> &Str {
51        // SAFETY: `Str` is `repr(transparent)` and so `&str` and `&Str`
52        //         can trivially be converted into each other.
53        unsafe { transmute::<&str, &Str>(self.deref()) }
54    }
55}
56
57impl Deref for Str {
58    type Target = str;
59
60    fn deref(&self) -> &Self::Target {
61        &self.0
62    }
63}
64
65// For convenient use in `format!`, for example.
66impl Display for Str {
67    #[inline]
68    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
69        Display::fmt(&self.0, f)
70    }
71}
72
73/// A helper trait to abstracting over various string types, allowing
74/// for conversion into a `Cow<'static, Str>`. This is the `Cow` enabled
75/// equivalent of `ToString`.
76pub trait IntoCowStr: private::Sealed {
77    fn into_cow_str(self) -> Cow<'static, Str>;
78}
79
80impl IntoCowStr for &'static str {
81    fn into_cow_str(self) -> Cow<'static, Str> {
82        // SAFETY: `Str` is `repr(transparent)` and so `&str` and `&Str`
83        //         can trivially be converted into each other.
84        let other = unsafe { transmute::<&str, &Str>(self) };
85        Cow::Borrowed(other)
86    }
87}
88
89impl IntoCowStr for String {
90    fn into_cow_str(self) -> Cow<'static, Str> {
91        Cow::Owned(self.into_boxed_str())
92    }
93}
94
95// TODO: We may want to support optionally storing a backtrace in
96//       terminal variants.
97enum ErrorImpl {
98    Io(io::Error),
99    // Unfortunately, if we just had a single `Context` variant that
100    // contains a `Cow`, this inner `Cow` would cause an overall enum
101    // size increase by a machine word, because currently `rustc`
102    // seemingly does not fold the necessary bits into the outer enum.
103    // We have two variants to work around that until `rustc` is smart
104    // enough.
105    ContextOwned {
106        context: Box<str>,
107        source: Box<Self>,
108    },
109    ContextStatic {
110        context: &'static str,
111        source: Box<Self>,
112    },
113}
114
115impl ErrorImpl {
116    fn kind(&self) -> ErrorKind {
117        match self {
118            Self::Io(error) => match error.kind() {
119                io::ErrorKind::NotFound => ErrorKind::NotFound,
120                io::ErrorKind::PermissionDenied => ErrorKind::PermissionDenied,
121                io::ErrorKind::AlreadyExists => ErrorKind::AlreadyExists,
122                io::ErrorKind::WouldBlock => ErrorKind::WouldBlock,
123                io::ErrorKind::InvalidInput => ErrorKind::InvalidInput,
124                io::ErrorKind::InvalidData => ErrorKind::InvalidData,
125                io::ErrorKind::TimedOut => ErrorKind::TimedOut,
126                io::ErrorKind::WriteZero => ErrorKind::WriteZero,
127                io::ErrorKind::Interrupted => ErrorKind::Interrupted,
128                io::ErrorKind::Unsupported => ErrorKind::Unsupported,
129                io::ErrorKind::UnexpectedEof => ErrorKind::UnexpectedEof,
130                io::ErrorKind::OutOfMemory => ErrorKind::OutOfMemory,
131                // TODO: Use `io::ErrorKind::ArgumentListTooLong` once
132                //       stable.
133                _ if error.raw_os_error() == Some(libc::E2BIG) => ErrorKind::TooBig,
134                _ => ErrorKind::Other,
135            },
136            Self::ContextOwned { source, .. } | Self::ContextStatic { source, .. } => {
137                source.deref().kind()
138            }
139        }
140    }
141
142    #[cfg(test)]
143    fn is_owned(&self) -> Option<bool> {
144        match self {
145            Self::ContextOwned { .. } => Some(true),
146            Self::ContextStatic { .. } => Some(false),
147            _ => None,
148        }
149    }
150}
151
152impl Debug for ErrorImpl {
153    // We try to mirror roughly how anyhow's Error is behaving, because
154    // that makes the most sense.
155    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
156        if f.alternate() {
157            let mut dbg;
158
159            match self {
160                Self::Io(io) => {
161                    dbg = f.debug_tuple(stringify!(Io));
162                    dbg.field(io)
163                }
164                Self::ContextOwned { context, .. } => {
165                    dbg = f.debug_tuple(stringify!(ContextOwned));
166                    dbg.field(context)
167                }
168                Self::ContextStatic { context, .. } => {
169                    dbg = f.debug_tuple(stringify!(ContextStatic));
170                    dbg.field(context)
171                }
172            }
173            .finish()
174        } else {
175            let () = match self {
176                Self::Io(error) => write!(f, "Error: {error}")?,
177                Self::ContextOwned { context, .. } => write!(f, "Error: {context}")?,
178                Self::ContextStatic { context, .. } => write!(f, "Error: {context}")?,
179            };
180
181            if let Some(source) = self.source() {
182                let () = f.write_str("\n\nCaused by:")?;
183
184                let mut error = Some(source);
185                while let Some(err) = error {
186                    let () = write!(f, "\n    {err:}")?;
187                    error = err.source();
188                }
189            }
190            Ok(())
191        }
192    }
193}
194
195impl Display for ErrorImpl {
196    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
197        let () = match self {
198            Self::Io(error) => Display::fmt(error, f)?,
199            Self::ContextOwned { context, .. } => Display::fmt(context, f)?,
200            Self::ContextStatic { context, .. } => Display::fmt(context, f)?,
201        };
202
203        if f.alternate() {
204            let mut error = self.source();
205            while let Some(err) = error {
206                let () = write!(f, ": {err}")?;
207                error = err.source();
208            }
209        }
210        Ok(())
211    }
212}
213
214impl error::Error for ErrorImpl {
215    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
216        match self {
217            Self::Io(error) => error.source(),
218            Self::ContextOwned { source, .. } | Self::ContextStatic { source, .. } => Some(source),
219        }
220    }
221}
222
223/// An enum providing a rough classification of errors.
224///
225/// The variants of this type partly resemble those of
226/// [`std::io::Error`], because these are the most common sources of
227/// error that the crate concerns itself with.
228#[derive(Clone, Copy, Debug, PartialEq)]
229#[non_exhaustive]
230pub enum ErrorKind {
231    /// An entity was not found, often a file.
232    NotFound,
233    /// The operation lacked the necessary privileges to complete.
234    PermissionDenied,
235    /// An entity already exists, often a file.
236    AlreadyExists,
237    /// The operation needs to block to complete, but the blocking
238    /// operation was requested to not occur.
239    WouldBlock,
240    /// A parameter was incorrect.
241    InvalidInput,
242    /// Data not valid for the operation were encountered.
243    InvalidData,
244    /// The I/O operation's timeout expired, causing it to be canceled.
245    TimedOut,
246    /// An error returned when an operation could not be completed
247    /// because a call to [`write`] returned [`Ok(0)`][Result::Ok].
248    WriteZero,
249    /// This operation was interrupted.
250    ///
251    /// Interrupted operations can typically be retried.
252    Interrupted,
253    /// This operation is unsupported on this platform.
254    Unsupported,
255    /// An error returned when an operation could not be completed
256    /// because an "end of file" was reached prematurely.
257    UnexpectedEof,
258    /// An operation could not be completed, because it failed
259    /// to allocate enough memory.
260    OutOfMemory,
261    /// An argument exceeded a size limit imposed by the kernel.
262    ///
263    /// Corresponds to `E2BIG` from the underlying syscall. For BPF map
264    /// operations such as [`MapCore::update`][crate::MapCore::update]
265    /// this typically means the map has reached its `max_entries`
266    /// limit. The same code can also indicate that a BPF program is too
267    /// large to load.
268    TooBig,
269    /// A custom error that does not fall under any other I/O error
270    /// kind.
271    Other,
272}
273
274/// The error type used by the library.
275///
276/// Errors generally form a chain, with higher-level errors typically
277/// providing additional context for lower level ones. E.g., an IO error
278/// such as file-not-found could be reported by a system level API (such
279/// as [`std::fs::File::open`]) and may be contextualized with the path
280/// to the file attempted to be opened.
281///
282/// ```
283/// use std::fs::File;
284/// use std::error::Error as _;
285/// # use libbpf_rs::ErrorExt as _;
286///
287/// let path = "/does-not-exist";
288/// let result = File::open(path).with_context(|| format!("failed to open {path}"));
289///
290/// let err = result.unwrap_err();
291/// assert_eq!(err.to_string(), "failed to open /does-not-exist");
292///
293/// // Retrieve the underlying error.
294/// let inner_err = err.source().unwrap();
295/// assert!(inner_err.to_string().starts_with("No such file or directory"));
296/// ```
297///
298/// For convenient reporting, the [`Display`][std::fmt::Display]
299/// representation takes care of reporting the complete error chain when
300/// the alternate flag is set:
301/// ```
302/// # use std::fs::File;
303/// # use std::error::Error as _;
304/// # use libbpf_rs::ErrorExt as _;
305/// # let path = "/does-not-exist";
306/// # let result = File::open(path).with_context(|| format!("failed to open {path}"));
307/// # let err = result.unwrap_err();
308/// // > failed to open /does-not-exist: No such file or directory (os error 2)
309/// println!("{err:#}");
310/// ```
311///
312/// The [`Debug`][std::fmt::Debug] representation similarly will print
313/// the entire error chain, but will do so in a multi-line format:
314/// ```
315/// # use std::fs::File;
316/// # use std::error::Error as _;
317/// # use libbpf_rs::ErrorExt as _;
318/// # let path = "/does-not-exist";
319/// # let result = File::open(path).with_context(|| format!("failed to open {path}"));
320/// # let err = result.unwrap_err();
321/// // > Error: failed to open /does-not-exist
322/// // >
323/// // > Caused by:
324/// // >     No such file or directory (os error 2)
325/// println!("{err:?}");
326/// ```
327// Representation is optimized for fast copying (a single machine word),
328// not so much for fast creation (as it is heap allocated). We generally
329// expect errors to be exceptional, though a lot of functionality is
330// fallible (i.e., returns a `Result<T, Error>` which would be penalized
331// by a large `Err` variant).
332#[repr(transparent)]
333pub struct Error {
334    /// The top-most error of the chain.
335    error: Box<ErrorImpl>,
336}
337
338impl Error {
339    /// Create an [`Error`] from an OS error code (typically `errno`).
340    ///
341    /// # Notes
342    /// An OS error code should always be positive.
343    #[inline]
344    pub fn from_raw_os_error(code: i32) -> Self {
345        debug_assert!(
346            code > 0,
347            "OS error code should be positive integer; got: {code}"
348        );
349        Self::from(io::Error::from_raw_os_error(code))
350    }
351
352    #[inline]
353    pub(crate) fn with_io_error<E>(kind: io::ErrorKind, error: E) -> Self
354    where
355        E: ToString,
356    {
357        Self::from(io::Error::new(kind, error.to_string()))
358    }
359
360    #[inline]
361    pub(crate) fn with_invalid_data<E>(error: E) -> Self
362    where
363        E: ToString,
364    {
365        Self::with_io_error(io::ErrorKind::InvalidData, error)
366    }
367
368    #[inline]
369    pub(crate) fn with_invalid_input<E>(error: E) -> Self
370    where
371        E: ToString,
372    {
373        Self::with_io_error(io::ErrorKind::InvalidInput, error)
374    }
375
376    /// Retrieve a rough error classification in the form of an
377    /// [`ErrorKind`].
378    #[inline]
379    pub fn kind(&self) -> ErrorKind {
380        self.error.kind()
381    }
382
383    /// Layer the provided context on top of this `Error`, creating a
384    /// new one in the process.
385    fn layer_context(self, context: Cow<'static, Str>) -> Self {
386        match context {
387            Cow::Owned(context) => Self {
388                error: Box::new(ErrorImpl::ContextOwned {
389                    context,
390                    source: self.error,
391                }),
392            },
393            Cow::Borrowed(context) => Self {
394                error: Box::new(ErrorImpl::ContextStatic {
395                    context,
396                    source: self.error,
397                }),
398            },
399        }
400    }
401}
402
403impl Debug for Error {
404    #[inline]
405    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
406        Debug::fmt(&self.error, f)
407    }
408}
409
410impl Display for Error {
411    #[inline]
412    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
413        Display::fmt(&self.error, f)
414    }
415}
416
417impl error::Error for Error {
418    #[inline]
419    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
420        self.error.source()
421    }
422}
423
424impl From<io::Error> for Error {
425    fn from(other: io::Error) -> Self {
426        Self {
427            error: Box::new(ErrorImpl::Io(other)),
428        }
429    }
430}
431
432/// A trait providing ergonomic chaining capabilities to [`Error`].
433pub trait ErrorExt: private::Sealed {
434    /// The output type produced by [`context`](Self::context) and
435    /// [`with_context`](Self::with_context).
436    type Output;
437
438    /// Add context to this error.
439    // If we had specialization of sorts we could be more lenient as to
440    // what we can accept, but for now this method always works with
441    // static strings and nothing else.
442    fn context<C>(self, context: C) -> Self::Output
443    where
444        C: IntoCowStr;
445
446    /// Add context to this error, using a closure for lazy evaluation.
447    fn with_context<C, F>(self, f: F) -> Self::Output
448    where
449        C: IntoCowStr,
450        F: FnOnce() -> C;
451}
452
453impl ErrorExt for Error {
454    type Output = Self;
455
456    fn context<C>(self, context: C) -> Self::Output
457    where
458        C: IntoCowStr,
459    {
460        self.layer_context(context.into_cow_str())
461    }
462
463    fn with_context<C, F>(self, f: F) -> Self::Output
464    where
465        C: IntoCowStr,
466        F: FnOnce() -> C,
467    {
468        self.layer_context(f().into_cow_str())
469    }
470}
471
472impl<T, E> ErrorExt for Result<T, E>
473where
474    E: ErrorExt,
475{
476    type Output = Result<T, E::Output>;
477
478    fn context<C>(self, context: C) -> Self::Output
479    where
480        C: IntoCowStr,
481    {
482        match self {
483            Ok(val) => Ok(val),
484            Err(err) => Err(err.context(context)),
485        }
486    }
487
488    fn with_context<C, F>(self, f: F) -> Self::Output
489    where
490        C: IntoCowStr,
491        F: FnOnce() -> C,
492    {
493        match self {
494            Ok(val) => Ok(val),
495            Err(err) => Err(err.with_context(f)),
496        }
497    }
498}
499
500impl ErrorExt for io::Error {
501    type Output = Error;
502
503    fn context<C>(self, context: C) -> Self::Output
504    where
505        C: IntoCowStr,
506    {
507        Error::from(self).context(context)
508    }
509
510    fn with_context<C, F>(self, f: F) -> Self::Output
511    where
512        C: IntoCowStr,
513        F: FnOnce() -> C,
514    {
515        Error::from(self).with_context(f)
516    }
517}
518
519/// A trait providing conversion shortcuts for creating `Error`
520/// instances.
521pub trait IntoError<T>: private::Sealed
522where
523    Self: Sized,
524{
525    fn ok_or_error<C, F>(self, kind: io::ErrorKind, f: F) -> Result<T, Error>
526    where
527        C: ToString,
528        F: FnOnce() -> C;
529
530    #[inline]
531    fn ok_or_invalid_data<C, F>(self, f: F) -> Result<T, Error>
532    where
533        C: ToString,
534        F: FnOnce() -> C,
535    {
536        self.ok_or_error(io::ErrorKind::InvalidData, f)
537    }
538}
539
540impl<T> IntoError<T> for Option<T> {
541    #[inline]
542    fn ok_or_error<C, F>(self, kind: io::ErrorKind, f: F) -> Result<T, Error>
543    where
544        C: ToString,
545        F: FnOnce() -> C,
546    {
547        self.ok_or_else(|| Error::with_io_error(kind, f().to_string()))
548    }
549}
550
551#[cfg(test)]
552mod tests {
553    use super::*;
554
555    use std::mem::size_of;
556
557    /// Check various features of our `Str` wrapper type.
558    #[test]
559    fn str_wrapper() {
560        let b = "test string".to_string().into_boxed_str();
561        let s: &Str = b.borrow();
562        let _b: Box<str> = s.to_owned();
563
564        assert_eq!(s.to_string(), b.deref());
565        assert_eq!(format!("{s:?}"), "Str(\"test string\")");
566    }
567
568    /// Check that our `Error` type's size is as expected.
569    #[test]
570    fn error_size() {
571        assert_eq!(size_of::<Error>(), size_of::<usize>());
572        assert_eq!(size_of::<ErrorImpl>(), 4 * size_of::<usize>());
573    }
574
575    /// Check that we can format errors as expected.
576    #[test]
577    fn error_formatting() {
578        let err = io::Error::new(io::ErrorKind::InvalidData, "some invalid data");
579        let err = Error::from(err);
580
581        let src = err.source();
582        assert!(src.is_none(), "{src:?}");
583        assert!(err.error.is_owned().is_none());
584        assert_eq!(err.kind(), ErrorKind::InvalidData);
585        assert_eq!(format!("{err}"), "some invalid data");
586        assert_eq!(format!("{err:#}"), "some invalid data");
587        assert_eq!(format!("{err:?}"), "Error: some invalid data");
588        // TODO: The inner format may not actually be all that stable.
589        let expected = r#"Io(
590    Custom {
591        kind: InvalidData,
592        error: "some invalid data",
593    },
594)"#;
595        assert_eq!(format!("{err:#?}"), expected);
596
597        let err = err.context("inner context");
598        let src = err.source();
599        assert!(src.is_some(), "{src:?}");
600        assert!(!err.error.is_owned().unwrap());
601        assert_eq!(err.kind(), ErrorKind::InvalidData);
602        assert_eq!(format!("{err}"), "inner context");
603        assert_eq!(format!("{err:#}"), "inner context: some invalid data");
604
605        let expected = r#"Error: inner context
606
607Caused by:
608    some invalid data"#;
609        assert_eq!(format!("{err:?}"), expected);
610        // Nope, not going to bother.
611        assert_ne!(format!("{err:#?}"), "");
612
613        let err = err.context("outer context".to_string());
614        let src = err.source();
615        assert!(src.is_some(), "{src:?}");
616        assert!(err.error.is_owned().unwrap());
617        assert_eq!(err.kind(), ErrorKind::InvalidData);
618        assert_eq!(format!("{err}"), "outer context");
619        assert_eq!(
620            format!("{err:#}"),
621            "outer context: inner context: some invalid data"
622        );
623
624        let expected = r#"Error: outer context
625
626Caused by:
627    inner context
628    some invalid data"#;
629        assert_eq!(format!("{err:?}"), expected);
630        assert_ne!(format!("{err:#?}"), "");
631    }
632
633    /// Check that `E2BIG` is reported as [`ErrorKind::TooBig`].
634    #[test]
635    fn e2big_maps_to_too_big() {
636        let err = Error::from_raw_os_error(libc::E2BIG);
637        assert_eq!(err.kind(), ErrorKind::TooBig);
638
639        let err = err.context("inserting key into map");
640        assert_eq!(err.kind(), ErrorKind::TooBig);
641    }
642}