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<ErrorImpl>,
108    },
109    ContextStatic {
110        context: &'static str,
111        source: Box<ErrorImpl>,
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                _ => ErrorKind::Other,
132            },
133            Self::ContextOwned { source, .. } | Self::ContextStatic { source, .. } => {
134                source.deref().kind()
135            }
136        }
137    }
138
139    #[cfg(test)]
140    fn is_owned(&self) -> Option<bool> {
141        match self {
142            Self::ContextOwned { .. } => Some(true),
143            Self::ContextStatic { .. } => Some(false),
144            _ => None,
145        }
146    }
147}
148
149impl Debug for ErrorImpl {
150    // We try to mirror roughly how anyhow's Error is behaving, because
151    // that makes the most sense.
152    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
153        if f.alternate() {
154            let mut dbg;
155
156            match self {
157                Self::Io(io) => {
158                    dbg = f.debug_tuple(stringify!(Io));
159                    dbg.field(io)
160                }
161                Self::ContextOwned { context, .. } => {
162                    dbg = f.debug_tuple(stringify!(ContextOwned));
163                    dbg.field(context)
164                }
165                Self::ContextStatic { context, .. } => {
166                    dbg = f.debug_tuple(stringify!(ContextStatic));
167                    dbg.field(context)
168                }
169            }
170            .finish()
171        } else {
172            let () = match self {
173                Self::Io(error) => write!(f, "Error: {error}")?,
174                Self::ContextOwned { context, .. } => write!(f, "Error: {context}")?,
175                Self::ContextStatic { context, .. } => write!(f, "Error: {context}")?,
176            };
177
178            if let Some(source) = self.source() {
179                let () = f.write_str("\n\nCaused by:")?;
180
181                let mut error = Some(source);
182                while let Some(err) = error {
183                    let () = write!(f, "\n    {err:}")?;
184                    error = err.source();
185                }
186            }
187            Ok(())
188        }
189    }
190}
191
192impl Display for ErrorImpl {
193    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
194        let () = match self {
195            Self::Io(error) => Display::fmt(error, f)?,
196            Self::ContextOwned { context, .. } => Display::fmt(context, f)?,
197            Self::ContextStatic { context, .. } => Display::fmt(context, f)?,
198        };
199
200        if f.alternate() {
201            let mut error = self.source();
202            while let Some(err) = error {
203                let () = write!(f, ": {err}")?;
204                error = err.source();
205            }
206        }
207        Ok(())
208    }
209}
210
211impl error::Error for ErrorImpl {
212    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
213        match self {
214            Self::Io(error) => error.source(),
215            Self::ContextOwned { source, .. } | Self::ContextStatic { source, .. } => Some(source),
216        }
217    }
218}
219
220/// An enum providing a rough classification of errors.
221///
222/// The variants of this type partly resemble those of
223/// [`std::io::Error`], because these are the most common sources of
224/// error that the crate concerns itself with.
225#[derive(Clone, Copy, Debug, PartialEq)]
226#[non_exhaustive]
227pub enum ErrorKind {
228    /// An entity was not found, often a file.
229    NotFound,
230    /// The operation lacked the necessary privileges to complete.
231    PermissionDenied,
232    /// An entity already exists, often a file.
233    AlreadyExists,
234    /// The operation needs to block to complete, but the blocking
235    /// operation was requested to not occur.
236    WouldBlock,
237    /// A parameter was incorrect.
238    InvalidInput,
239    /// Data not valid for the operation were encountered.
240    InvalidData,
241    /// The I/O operation's timeout expired, causing it to be canceled.
242    TimedOut,
243    /// An error returned when an operation could not be completed
244    /// because a call to [`write`] returned [`Ok(0)`].
245    WriteZero,
246    /// This operation was interrupted.
247    ///
248    /// Interrupted operations can typically be retried.
249    Interrupted,
250    /// This operation is unsupported on this platform.
251    Unsupported,
252    /// An error returned when an operation could not be completed
253    /// because an "end of file" was reached prematurely.
254    UnexpectedEof,
255    /// An operation could not be completed, because it failed
256    /// to allocate enough memory.
257    OutOfMemory,
258    /// A custom error that does not fall under any other I/O error
259    /// kind.
260    Other,
261}
262
263/// The error type used by the library.
264///
265/// Errors generally form a chain, with higher-level errors typically
266/// providing additional context for lower level ones. E.g., an IO error
267/// such as file-not-found could be reported by a system level API (such
268/// as [`std::fs::File::open`]) and may be contextualized with the path
269/// to the file attempted to be opened.
270///
271/// ```
272/// use std::fs::File;
273/// use std::error::Error as _;
274/// # use libbpf_rs::ErrorExt as _;
275///
276/// let path = "/does-not-exist";
277/// let result = File::open(path).with_context(|| format!("failed to open {path}"));
278///
279/// let err = result.unwrap_err();
280/// assert_eq!(err.to_string(), "failed to open /does-not-exist");
281///
282/// // Retrieve the underlying error.
283/// let inner_err = err.source().unwrap();
284/// assert!(inner_err.to_string().starts_with("No such file or directory"));
285/// ```
286///
287/// For convenient reporting, the [`Display`][std::fmt::Display]
288/// representation takes care of reporting the complete error chain when
289/// the alternate flag is set:
290/// ```
291/// # use std::fs::File;
292/// # use std::error::Error as _;
293/// # use libbpf_rs::ErrorExt as _;
294/// # let path = "/does-not-exist";
295/// # let result = File::open(path).with_context(|| format!("failed to open {path}"));
296/// # let err = result.unwrap_err();
297/// // > failed to open /does-not-exist: No such file or directory (os error 2)
298/// println!("{err:#}");
299/// ```
300///
301/// The [`Debug`][std::fmt::Debug] representation similarly will print
302/// the entire error chain, but will do so in a multi-line format:
303/// ```
304/// # use std::fs::File;
305/// # use std::error::Error as _;
306/// # use libbpf_rs::ErrorExt as _;
307/// # let path = "/does-not-exist";
308/// # let result = File::open(path).with_context(|| format!("failed to open {path}"));
309/// # let err = result.unwrap_err();
310/// // > Error: failed to open /does-not-exist
311/// // >
312/// // > Caused by:
313/// // >     No such file or directory (os error 2)
314/// println!("{err:?}");
315/// ```
316// Representation is optimized for fast copying (a single machine word),
317// not so much for fast creation (as it is heap allocated). We generally
318// expect errors to be exceptional, though a lot of functionality is
319// fallible (i.e., returns a `Result<T, Error>` which would be penalized
320// by a large `Err` variant).
321#[repr(transparent)]
322pub struct Error {
323    /// The top-most error of the chain.
324    error: Box<ErrorImpl>,
325}
326
327impl Error {
328    /// Create an [`Error`] from an OS error code (typically `errno`).
329    ///
330    /// # Notes
331    /// An OS error code should always be positive.
332    #[inline]
333    pub fn from_raw_os_error(code: i32) -> Self {
334        debug_assert!(
335            code > 0,
336            "OS error code should be positive integer; got: {code}"
337        );
338        Self::from(io::Error::from_raw_os_error(code))
339    }
340
341    #[inline]
342    pub(crate) fn with_io_error<E>(kind: io::ErrorKind, error: E) -> Self
343    where
344        E: ToString,
345    {
346        Self::from(io::Error::new(kind, error.to_string()))
347    }
348
349    #[inline]
350    pub(crate) fn with_invalid_data<E>(error: E) -> Self
351    where
352        E: ToString,
353    {
354        Self::with_io_error(io::ErrorKind::InvalidData, error)
355    }
356
357    /// Retrieve a rough error classification in the form of an
358    /// [`ErrorKind`].
359    #[inline]
360    pub fn kind(&self) -> ErrorKind {
361        self.error.kind()
362    }
363
364    /// Layer the provided context on top of this `Error`, creating a
365    /// new one in the process.
366    fn layer_context(self, context: Cow<'static, Str>) -> Self {
367        match context {
368            Cow::Owned(context) => Self {
369                error: Box::new(ErrorImpl::ContextOwned {
370                    context,
371                    source: self.error,
372                }),
373            },
374            Cow::Borrowed(context) => Self {
375                error: Box::new(ErrorImpl::ContextStatic {
376                    context,
377                    source: self.error,
378                }),
379            },
380        }
381    }
382}
383
384impl Debug for Error {
385    #[inline]
386    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
387        Debug::fmt(&self.error, f)
388    }
389}
390
391impl Display for Error {
392    #[inline]
393    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
394        Display::fmt(&self.error, f)
395    }
396}
397
398impl error::Error for Error {
399    #[inline]
400    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
401        self.error.source()
402    }
403}
404
405impl From<io::Error> for Error {
406    fn from(other: io::Error) -> Self {
407        Self {
408            error: Box::new(ErrorImpl::Io(other)),
409        }
410    }
411}
412
413/// A trait providing ergonomic chaining capabilities to [`Error`].
414pub trait ErrorExt: private::Sealed {
415    /// The output type produced by [`context`](Self::context) and
416    /// [`with_context`](Self::with_context).
417    type Output;
418
419    /// Add context to this error.
420    // If we had specialization of sorts we could be more lenient as to
421    // what we can accept, but for now this method always works with
422    // static strings and nothing else.
423    fn context<C>(self, context: C) -> Self::Output
424    where
425        C: IntoCowStr;
426
427    /// Add context to this error, using a closure for lazy evaluation.
428    fn with_context<C, F>(self, f: F) -> Self::Output
429    where
430        C: IntoCowStr,
431        F: FnOnce() -> C;
432}
433
434impl ErrorExt for Error {
435    type Output = Error;
436
437    fn context<C>(self, context: C) -> Self::Output
438    where
439        C: IntoCowStr,
440    {
441        self.layer_context(context.into_cow_str())
442    }
443
444    fn with_context<C, F>(self, f: F) -> Self::Output
445    where
446        C: IntoCowStr,
447        F: FnOnce() -> C,
448    {
449        self.layer_context(f().into_cow_str())
450    }
451}
452
453impl<T, E> ErrorExt for Result<T, E>
454where
455    E: ErrorExt,
456{
457    type Output = Result<T, E::Output>;
458
459    fn context<C>(self, context: C) -> Self::Output
460    where
461        C: IntoCowStr,
462    {
463        match self {
464            Ok(val) => Ok(val),
465            Err(err) => Err(err.context(context)),
466        }
467    }
468
469    fn with_context<C, F>(self, f: F) -> Self::Output
470    where
471        C: IntoCowStr,
472        F: FnOnce() -> C,
473    {
474        match self {
475            Ok(val) => Ok(val),
476            Err(err) => Err(err.with_context(f)),
477        }
478    }
479}
480
481impl ErrorExt for io::Error {
482    type Output = Error;
483
484    fn context<C>(self, context: C) -> Self::Output
485    where
486        C: IntoCowStr,
487    {
488        Error::from(self).context(context)
489    }
490
491    fn with_context<C, F>(self, f: F) -> Self::Output
492    where
493        C: IntoCowStr,
494        F: FnOnce() -> C,
495    {
496        Error::from(self).with_context(f)
497    }
498}
499
500/// A trait providing conversion shortcuts for creating `Error`
501/// instances.
502pub trait IntoError<T>: private::Sealed
503where
504    Self: Sized,
505{
506    fn ok_or_error<C, F>(self, kind: io::ErrorKind, f: F) -> Result<T, Error>
507    where
508        C: ToString,
509        F: FnOnce() -> C;
510
511    #[inline]
512    fn ok_or_invalid_data<C, F>(self, f: F) -> Result<T, Error>
513    where
514        C: ToString,
515        F: FnOnce() -> C,
516    {
517        self.ok_or_error(io::ErrorKind::InvalidData, f)
518    }
519}
520
521impl<T> IntoError<T> for Option<T> {
522    #[inline]
523    fn ok_or_error<C, F>(self, kind: io::ErrorKind, f: F) -> Result<T, Error>
524    where
525        C: ToString,
526        F: FnOnce() -> C,
527    {
528        self.ok_or_else(|| Error::with_io_error(kind, f().to_string()))
529    }
530}
531
532#[cfg(test)]
533mod tests {
534    use super::*;
535
536    use std::mem::size_of;
537
538    /// Check various features of our `Str` wrapper type.
539    #[test]
540    fn str_wrapper() {
541        let b = "test string".to_string().into_boxed_str();
542        let s: &Str = b.borrow();
543        let _b: Box<str> = s.to_owned();
544
545        assert_eq!(s.to_string(), b.deref());
546        assert_eq!(format!("{s:?}"), "Str(\"test string\")");
547    }
548
549    /// Check that our `Error` type's size is as expected.
550    #[test]
551    fn error_size() {
552        assert_eq!(size_of::<Error>(), size_of::<usize>());
553        assert_eq!(size_of::<ErrorImpl>(), 4 * size_of::<usize>());
554    }
555
556    /// Check that we can format errors as expected.
557    #[test]
558    fn error_formatting() {
559        let err = io::Error::new(io::ErrorKind::InvalidData, "some invalid data");
560        let err = Error::from(err);
561
562        let src = err.source();
563        assert!(src.is_none(), "{src:?}");
564        assert!(err.error.is_owned().is_none());
565        assert_eq!(err.kind(), ErrorKind::InvalidData);
566        assert_eq!(format!("{err}"), "some invalid data");
567        assert_eq!(format!("{err:#}"), "some invalid data");
568        assert_eq!(format!("{err:?}"), "Error: some invalid data");
569        // TODO: The inner format may not actually be all that stable.
570        let expected = r#"Io(
571    Custom {
572        kind: InvalidData,
573        error: "some invalid data",
574    },
575)"#;
576        assert_eq!(format!("{err:#?}"), expected);
577
578        let err = err.context("inner context");
579        let src = err.source();
580        assert!(src.is_some(), "{src:?}");
581        assert!(!err.error.is_owned().unwrap());
582        assert_eq!(err.kind(), ErrorKind::InvalidData);
583        assert_eq!(format!("{err}"), "inner context");
584        assert_eq!(format!("{err:#}"), "inner context: some invalid data");
585
586        let expected = r#"Error: inner context
587
588Caused by:
589    some invalid data"#;
590        assert_eq!(format!("{err:?}"), expected);
591        // Nope, not going to bother.
592        assert_ne!(format!("{err:#?}"), "");
593
594        let err = err.context("outer context".to_string());
595        let src = err.source();
596        assert!(src.is_some(), "{src:?}");
597        assert!(err.error.is_owned().unwrap());
598        assert_eq!(err.kind(), ErrorKind::InvalidData);
599        assert_eq!(format!("{err}"), "outer context");
600        assert_eq!(
601            format!("{err:#}"),
602            "outer context: inner context: some invalid data"
603        );
604
605        let expected = r#"Error: outer context
606
607Caused by:
608    inner context
609    some invalid data"#;
610        assert_eq!(format!("{err:?}"), expected);
611        assert_ne!(format!("{err:#?}"), "");
612    }
613}