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`] representation takes care
299/// of reporting the complete error chain when the alternate flag is
300/// 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`] representation similarly will print the entire error
313/// 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)]
333#[doc(alias = "libbpf_get_error")]
334pub struct Error {
335    /// The top-most error of the chain.
336    error: Box<ErrorImpl>,
337}
338
339impl Error {
340    /// Create an [`Error`] from an OS error code (typically `errno`).
341    ///
342    /// # Notes
343    /// An OS error code should always be positive.
344    #[inline]
345    pub fn from_raw_os_error(code: i32) -> Self {
346        debug_assert!(
347            code > 0,
348            "OS error code should be positive integer; got: {code}"
349        );
350        Self::from(io::Error::from_raw_os_error(code))
351    }
352
353    #[inline]
354    pub(crate) fn with_io_error<E>(kind: io::ErrorKind, error: E) -> Self
355    where
356        E: ToString,
357    {
358        Self::from(io::Error::new(kind, error.to_string()))
359    }
360
361    #[inline]
362    pub(crate) fn with_invalid_data<E>(error: E) -> Self
363    where
364        E: ToString,
365    {
366        Self::with_io_error(io::ErrorKind::InvalidData, error)
367    }
368
369    #[inline]
370    pub(crate) fn with_invalid_input<E>(error: E) -> Self
371    where
372        E: ToString,
373    {
374        Self::with_io_error(io::ErrorKind::InvalidInput, error)
375    }
376
377    /// Retrieve a rough error classification in the form of an
378    /// [`ErrorKind`].
379    #[inline]
380    pub fn kind(&self) -> ErrorKind {
381        self.error.kind()
382    }
383
384    /// Layer the provided context on top of this `Error`, creating a
385    /// new one in the process.
386    fn layer_context(self, context: Cow<'static, Str>) -> Self {
387        match context {
388            Cow::Owned(context) => Self {
389                error: Box::new(ErrorImpl::ContextOwned {
390                    context,
391                    source: self.error,
392                }),
393            },
394            Cow::Borrowed(context) => Self {
395                error: Box::new(ErrorImpl::ContextStatic {
396                    context,
397                    source: self.error,
398                }),
399            },
400        }
401    }
402}
403
404impl Debug for Error {
405    #[inline]
406    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
407        Debug::fmt(&self.error, f)
408    }
409}
410
411impl Display for Error {
412    #[inline]
413    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
414        Display::fmt(&self.error, f)
415    }
416}
417
418impl error::Error for Error {
419    #[inline]
420    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
421        self.error.source()
422    }
423}
424
425impl From<io::Error> for Error {
426    fn from(other: io::Error) -> Self {
427        Self {
428            error: Box::new(ErrorImpl::Io(other)),
429        }
430    }
431}
432
433/// A trait providing ergonomic chaining capabilities to [`Error`].
434pub trait ErrorExt: private::Sealed {
435    /// The output type produced by [`context`](Self::context) and
436    /// [`with_context`](Self::with_context).
437    type Output;
438
439    /// Add context to this error.
440    // If we had specialization of sorts we could be more lenient as to
441    // what we can accept, but for now this method always works with
442    // static strings and nothing else.
443    fn context<C>(self, context: C) -> Self::Output
444    where
445        C: IntoCowStr;
446
447    /// Add context to this error, using a closure for lazy evaluation.
448    fn with_context<C, F>(self, f: F) -> Self::Output
449    where
450        C: IntoCowStr,
451        F: FnOnce() -> C;
452}
453
454impl ErrorExt for Error {
455    type Output = Self;
456
457    fn context<C>(self, context: C) -> Self::Output
458    where
459        C: IntoCowStr,
460    {
461        self.layer_context(context.into_cow_str())
462    }
463
464    fn with_context<C, F>(self, f: F) -> Self::Output
465    where
466        C: IntoCowStr,
467        F: FnOnce() -> C,
468    {
469        self.layer_context(f().into_cow_str())
470    }
471}
472
473impl<T, E> ErrorExt for Result<T, E>
474where
475    E: ErrorExt,
476{
477    type Output = Result<T, E::Output>;
478
479    fn context<C>(self, context: C) -> Self::Output
480    where
481        C: IntoCowStr,
482    {
483        match self {
484            Ok(val) => Ok(val),
485            Err(err) => Err(err.context(context)),
486        }
487    }
488
489    fn with_context<C, F>(self, f: F) -> Self::Output
490    where
491        C: IntoCowStr,
492        F: FnOnce() -> C,
493    {
494        match self {
495            Ok(val) => Ok(val),
496            Err(err) => Err(err.with_context(f)),
497        }
498    }
499}
500
501impl ErrorExt for io::Error {
502    type Output = Error;
503
504    fn context<C>(self, context: C) -> Self::Output
505    where
506        C: IntoCowStr,
507    {
508        Error::from(self).context(context)
509    }
510
511    fn with_context<C, F>(self, f: F) -> Self::Output
512    where
513        C: IntoCowStr,
514        F: FnOnce() -> C,
515    {
516        Error::from(self).with_context(f)
517    }
518}
519
520/// A trait providing conversion shortcuts for creating `Error`
521/// instances.
522pub trait IntoError<T>: private::Sealed
523where
524    Self: Sized,
525{
526    fn ok_or_error<C, F>(self, kind: io::ErrorKind, f: F) -> Result<T, Error>
527    where
528        C: ToString,
529        F: FnOnce() -> C;
530
531    #[inline]
532    fn ok_or_invalid_data<C, F>(self, f: F) -> Result<T, Error>
533    where
534        C: ToString,
535        F: FnOnce() -> C,
536    {
537        self.ok_or_error(io::ErrorKind::InvalidData, f)
538    }
539}
540
541impl<T> IntoError<T> for Option<T> {
542    #[inline]
543    fn ok_or_error<C, F>(self, kind: io::ErrorKind, f: F) -> Result<T, Error>
544    where
545        C: ToString,
546        F: FnOnce() -> C,
547    {
548        self.ok_or_else(|| Error::with_io_error(kind, f().to_string()))
549    }
550}
551
552#[cfg(test)]
553mod tests {
554    use super::*;
555
556    use std::mem::size_of;
557
558    /// Check various features of our `Str` wrapper type.
559    #[test]
560    fn str_wrapper() {
561        let b = "test string".to_string().into_boxed_str();
562        let s: &Str = b.borrow();
563        let _b: Box<str> = s.to_owned();
564
565        assert_eq!(s.to_string(), b.deref());
566        assert_eq!(format!("{s:?}"), "Str(\"test string\")");
567    }
568
569    /// Check that our `Error` type's size is as expected.
570    #[test]
571    fn error_size() {
572        assert_eq!(size_of::<Error>(), size_of::<usize>());
573        assert_eq!(size_of::<ErrorImpl>(), 4 * size_of::<usize>());
574    }
575
576    /// Check that we can format errors as expected.
577    #[test]
578    fn error_formatting() {
579        let err = io::Error::new(io::ErrorKind::InvalidData, "some invalid data");
580        let err = Error::from(err);
581
582        let src = err.source();
583        assert!(src.is_none(), "{src:?}");
584        assert!(err.error.is_owned().is_none());
585        assert_eq!(err.kind(), ErrorKind::InvalidData);
586        assert_eq!(format!("{err}"), "some invalid data");
587        assert_eq!(format!("{err:#}"), "some invalid data");
588        assert_eq!(format!("{err:?}"), "Error: some invalid data");
589        // TODO: The inner format may not actually be all that stable.
590        let expected = r#"Io(
591    Custom {
592        kind: InvalidData,
593        error: "some invalid data",
594    },
595)"#;
596        assert_eq!(format!("{err:#?}"), expected);
597
598        let err = err.context("inner context");
599        let src = err.source();
600        assert!(src.is_some(), "{src:?}");
601        assert!(!err.error.is_owned().unwrap());
602        assert_eq!(err.kind(), ErrorKind::InvalidData);
603        assert_eq!(format!("{err}"), "inner context");
604        assert_eq!(format!("{err:#}"), "inner context: some invalid data");
605
606        let expected = r#"Error: inner context
607
608Caused by:
609    some invalid data"#;
610        assert_eq!(format!("{err:?}"), expected);
611        // Nope, not going to bother.
612        assert_ne!(format!("{err:#?}"), "");
613
614        let err = err.context("outer context".to_string());
615        let src = err.source();
616        assert!(src.is_some(), "{src:?}");
617        assert!(err.error.is_owned().unwrap());
618        assert_eq!(err.kind(), ErrorKind::InvalidData);
619        assert_eq!(format!("{err}"), "outer context");
620        assert_eq!(
621            format!("{err:#}"),
622            "outer context: inner context: some invalid data"
623        );
624
625        let expected = r#"Error: outer context
626
627Caused by:
628    inner context
629    some invalid data"#;
630        assert_eq!(format!("{err:?}"), expected);
631        assert_ne!(format!("{err:#?}"), "");
632    }
633
634    /// Check that `E2BIG` is reported as [`ErrorKind::TooBig`].
635    #[test]
636    fn e2big_maps_to_too_big() {
637        let err = Error::from_raw_os_error(libc::E2BIG);
638        assert_eq!(err.kind(), ErrorKind::TooBig);
639
640        let err = err.context("inserting key into map");
641        assert_eq!(err.kind(), ErrorKind::TooBig);
642    }
643}