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