1 use std::borrow::Borrow;
2 use std::borrow::Cow;
3 use std::error;
4 use std::error::Error as _;
5 use std::fmt::Debug;
6 use std::fmt::Display;
7 use std::fmt::Formatter;
8 use std::fmt::Result as FmtResult;
9 use std::io;
10 use std::mem::transmute;
11 use std::ops::Deref;
12 use std::result;
13 
14 /// A result type using our [`Error`] by default.
15 pub type Result<T, E = Error> = result::Result<T, E>;
16 
17 #[allow(clippy::wildcard_imports)]
18 mod 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)]
37 pub struct Str(str);
38 
39 impl ToOwned for Str {
40     type Owned = Box<str>;
41 
42     #[inline]
to_owned(&self) -> Self::Owned43     fn to_owned(&self) -> Self::Owned {
44         self.0.to_string().into_boxed_str()
45     }
46 }
47 
48 impl Borrow<Str> for Box<str> {
49     #[inline]
borrow(&self) -> &Str50     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 
57 impl Deref for Str {
58     type Target = str;
59 
deref(&self) -> &Self::Target60     fn deref(&self) -> &Self::Target {
61         &self.0
62     }
63 }
64 
65 // For convenient use in `format!`, for example.
66 impl Display for Str {
67     #[inline]
fmt(&self, f: &mut Formatter<'_>) -> FmtResult68     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`.
76 pub trait IntoCowStr: private::Sealed {
into_cow_str(self) -> Cow<'static, Str>77     fn into_cow_str(self) -> Cow<'static, Str>;
78 }
79 
80 impl IntoCowStr for &'static str {
into_cow_str(self) -> Cow<'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 
89 impl IntoCowStr for String {
into_cow_str(self) -> Cow<'static, Str>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.
97 enum 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 
115 impl ErrorImpl {
kind(&self) -> ErrorKind116     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)]
is_owned(&self) -> Option<bool>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 
149 impl Debug for ErrorImpl {
150     // We try to mirror roughly how anyhow's Error is behaving, because
151     // that makes the most sense.
fmt(&self, f: &mut Formatter<'_>) -> FmtResult152     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 
192 impl Display for ErrorImpl {
fmt(&self, f: &mut Formatter<'_>) -> FmtResult193     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 
211 impl error::Error for ErrorImpl {
source(&self) -> Option<&(dyn error::Error + 'static)>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]
227 pub 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)]
322 pub struct Error {
323     /// The top-most error of the chain.
324     error: Box<ErrorImpl>,
325 }
326 
327 impl 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]
from_raw_os_error(code: i32) -> Self333     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]
with_io_error<E>(kind: io::ErrorKind, error: E) -> Self where E: ToString,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]
with_invalid_data<E>(error: E) -> Self where E: ToString,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]
kind(&self) -> ErrorKind360     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.
layer_context(self, context: Cow<'static, Str>) -> Self366     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 
384 impl Debug for Error {
385     #[inline]
fmt(&self, f: &mut Formatter<'_>) -> FmtResult386     fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
387         Debug::fmt(&self.error, f)
388     }
389 }
390 
391 impl Display for Error {
392     #[inline]
fmt(&self, f: &mut Formatter<'_>) -> FmtResult393     fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
394         Display::fmt(&self.error, f)
395     }
396 }
397 
398 impl error::Error for Error {
399     #[inline]
source(&self) -> Option<&(dyn error::Error + 'static)>400     fn source(&self) -> Option<&(dyn error::Error + 'static)> {
401         self.error.source()
402     }
403 }
404 
405 impl From<io::Error> for Error {
from(other: io::Error) -> Self406     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`].
414 pub 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.
context<C>(self, context: C) -> Self::Output where C: IntoCowStr423     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.
with_context<C, F>(self, f: F) -> Self::Output where C: IntoCowStr, F: FnOnce() -> C428     fn with_context<C, F>(self, f: F) -> Self::Output
429     where
430         C: IntoCowStr,
431         F: FnOnce() -> C;
432 }
433 
434 impl ErrorExt for Error {
435     type Output = Error;
436 
context<C>(self, context: C) -> Self::Output where C: IntoCowStr,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 
with_context<C, F>(self, f: F) -> Self::Output where C: IntoCowStr, F: FnOnce() -> C,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 
453 impl<T, E> ErrorExt for Result<T, E>
454 where
455     E: ErrorExt,
456 {
457     type Output = Result<T, E::Output>;
458 
context<C>(self, context: C) -> Self::Output where C: IntoCowStr,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 
with_context<C, F>(self, f: F) -> Self::Output where C: IntoCowStr, F: FnOnce() -> C,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 
481 impl ErrorExt for io::Error {
482     type Output = Error;
483 
context<C>(self, context: C) -> Self::Output where C: IntoCowStr,484     fn context<C>(self, context: C) -> Self::Output
485     where
486         C: IntoCowStr,
487     {
488         Error::from(self).context(context)
489     }
490 
with_context<C, F>(self, f: F) -> Self::Output where C: IntoCowStr, F: FnOnce() -> C,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.
502 pub trait IntoError<T>: private::Sealed
503 where
504     Self: Sized,
505 {
ok_or_error<C, F>(self, kind: io::ErrorKind, f: F) -> Result<T, Error> where C: ToString, F: FnOnce() -> C506     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]
ok_or_invalid_data<C, F>(self, f: F) -> Result<T, Error> where C: ToString, F: FnOnce() -> C,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 
521 impl<T> IntoError<T> for Option<T> {
522     #[inline]
ok_or_error<C, F>(self, kind: io::ErrorKind, f: F) -> Result<T, Error> where C: ToString, F: FnOnce() -> C,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)]
533 mod tests {
534     use super::*;
535 
536     use std::mem::size_of;
537 
538     /// Check various features of our `Str` wrapper type.
539     #[test]
str_wrapper()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]
error_size()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]
error_formatting()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 
588 Caused 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 
607 Caused by:
608     inner context
609     some invalid data"#;
610         assert_eq!(format!("{err:?}"), expected);
611         assert_ne!(format!("{err:#?}"), "");
612     }
613 }
614