The wrappers for Error provide the following functionality: - a struct that implements std::error::Error and can be filled with information similar to what would go into a C Error* (location, message).
- functionality similar to error_prepend() via the "cause()" member of std::error::Error. - converting a Result<> into a value that can be returned to C, filling in an Error** (like error_propagate() would do) if the Result<> contains an error; useful for callbacks written in Rust - converting a C Error* into a Result that can be returned to Rust, useful for Rust wrappers of C functions Signed-off-by: Paolo Bonzini <pbonz...@redhat.com> --- qemu/src/lib.rs | 2 + qemu/src/util/error.rs | 241 +++++++++++++++++++++++++++++++++++++++++ qemu/src/util/mod.rs | 1 + 3 files changed, 244 insertions(+) create mode 100644 qemu/src/util/error.rs diff --git a/qemu/src/lib.rs b/qemu/src/lib.rs index c48edcf..5f926b8 100644 --- a/qemu/src/lib.rs +++ b/qemu/src/lib.rs @@ -4,7 +4,9 @@ pub mod bindings; pub mod util; +pub use util::error::Error; pub use util::foreign::CloneToForeign; pub use util::foreign::FromForeign; pub use util::foreign::IntoNative; pub use util::foreign::OwnedPointer; +pub type Result<T> = std::result::Result<T, Error>; diff --git a/qemu/src/util/error.rs b/qemu/src/util/error.rs new file mode 100644 index 0000000..e7e6f2e --- /dev/null +++ b/qemu/src/util/error.rs @@ -0,0 +1,241 @@ +//! Error class for QEMU Rust code +//! +//! @author Paolo Bonzini + +use crate::bindings; +use crate::bindings::error_free; +use crate::bindings::error_get_pretty; +use crate::bindings::error_setg_internal; + +use std::ffi::CStr; +use std::fmt::{self, Display}; +use std::ptr; + +use crate::util::foreign::{CloneToForeign, FromForeign, OwnedPointer}; + +#[derive(Debug, Default)] +pub struct Error { + msg: Option<String>, + /// Appends the print string of the error to the msg if not None + cause: Option<Box<dyn std::error::Error>>, + location: Option<(String, u32)>, +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + self.cause.as_deref() + } + + #[allow(deprecated)] + fn description(&self) -> &str { + self.msg + .as_deref() + .or_else(|| self.cause.as_deref().map(std::error::Error::description)) + .unwrap_or("error") + } +} + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut prefix = ""; + if let Some((ref file, line)) = self.location { + write!(f, "{}:{}", file, line)?; + prefix = ": "; + } + if let Some(ref msg) = self.msg { + write!(f, "{}{}", prefix, msg)?; + prefix = ": "; + } + if let Some(ref cause) = self.cause { + write!(f, "{}{}", prefix, cause)?; + } else if prefix.is_empty() { + f.write_str("unknown error")?; + } + Ok(()) + } +} + +impl From<&str> for Error { + fn from(msg: &str) -> Self { + Error { + msg: Some(String::from(msg)), + cause: None, + location: None, + } + } +} + +impl From<std::io::Error> for Error { + fn from(error: std::io::Error) -> Self { + Error { + msg: None, + cause: Some(Box::new(error)), + location: None, + } + } +} + +impl Error { + /// Create a new error, prepending `msg` to the + /// description of `cause` + pub fn with_error<E: std::error::Error + 'static>(msg: &str, cause: E) -> Self { + Error { + msg: Some(String::from(msg)), + cause: Some(Box::new(cause)), + location: None, + } + } + + /// Create a new error, prepending `file:line: msg` to the + /// description of `cause` + pub fn with_error_file_line<E: std::error::Error + 'static>( + msg: &str, + cause: E, + file: &str, + line: u32, + ) -> Self { + Error { + msg: Some(String::from(msg)), + cause: Some(Box::new(cause)), + location: Some((String::from(file), line)), + } + } + + /// Create a new error with format `file:line: msg` + pub fn with_file_line(msg: &str, file: &str, line: u32) -> Self { + Error { + msg: Some(String::from(msg)), + cause: None, + location: Some((String::from(file), line)), + } + } + + /// Consume a result, returning false if it is an error and + /// true if it is successful. The error is propagated into + /// `errp` like the C API `error_propagate` would do. + /// + /// # Safety + /// + /// `errp` must be valid; typically it is received from C code + pub unsafe fn bool_or_propagate<T>( + result: Result<(), Self>, + errp: *mut *mut bindings::Error, + ) -> bool { + Self::ok_or_propagate(result, errp).is_some() + } + + /// Consume a result, returning a `NULL` pointer if it is an + /// error and a C representation of the contents if it is + /// successful. The error is propagated into `errp` like + /// the C API `error_propagate` would do. + /// + /// # Safety + /// + /// `errp` must be valid; typically it is received from C code + pub unsafe fn ptr_or_propagate<T: CloneToForeign>( + result: Result<T, Self>, + errp: *mut *mut bindings::Error, + ) -> *mut T::Foreign { + Self::ok_or_propagate(result, errp).map_or(ptr::null_mut(), |ref x| { + CloneToForeign::clone_to_foreign_ptr(x) + }) + } + + /// Consume a result and return `self.ok()`, but also propagate a + /// possible error into `errp`, like the C API `error_propagate` + /// would do. + /// + /// # Safety + /// + /// `errp` must be valid; typically it is received from C code + pub unsafe fn ok_or_propagate<T>( + result: Result<T, Self>, + errp: *mut *mut bindings::Error, + ) -> Option<T> { + match result { + Ok(ok) => Some(ok), + Err(err) => { + err.propagate(errp); + None + } + } + } + + /// Equivalent of the C function `error_propagate`. Fill `*errp` + /// with the information container in `self` if `errp` is not NULL; + /// then consume it. + /// + /// # Safety + /// + /// `errp` must be valid; typically it is received from C code + pub unsafe fn propagate(self, errp: *mut *mut bindings::Error) { + if errp.is_null() { + return; + } + errp.write(self.clone_to_foreign_ptr()); + } + + /// Convert a C `Error*` into a Rust `Result`, using `Ok(Default::default())` + /// if `c_error` is NULL. + /// + /// # Safety + /// + /// `c_error` must be valid; typically it has been filled by a C + /// function. + pub unsafe fn err_or_default<T: Default>(c_error: *mut bindings::Error) -> Result<T, Self> { + Self::err_or_else(c_error, Default::default) + } + + /// Convert a C `Error*` into a Rust `Result`, calling `f()` to + /// obtain an `Ok` value if `c_error` is NULL. + /// + /// # Safety + /// + /// `c_error` must be valid; typically it has been filled by a C + /// function. + pub unsafe fn err_or_else<T, F: FnOnce() -> T>( + c_error: *mut bindings::Error, + f: F, + ) -> Result<T, Self> { + match Option::<Self>::from_foreign(c_error) { + None => Ok(f()), + Some(err) => Err(err), + } + } +} + +impl CloneToForeign for Error { + type Foreign = bindings::Error; + + fn clone_to_foreign(&self) -> OwnedPointer<Self> { + let mut x: *mut bindings::Error = ptr::null_mut(); + unsafe { + error_setg_internal( + &mut x, + ptr::null_mut(), // FIXME + 0, + ptr::null_mut(), // FIXME + c"%s".as_ptr(), + format!("{}", self), + ); + OwnedPointer::new(x) + } + } + + unsafe fn free_foreign(p: *mut bindings::Error) { + unsafe { + error_free(p); + } + } +} + +impl FromForeign for Error { + unsafe fn cloned_from_foreign(c_error: *const bindings::Error) -> Self { + let c_str = unsafe { CStr::from_ptr(error_get_pretty(c_error)) }; + Error { + msg: Some(c_str.to_string_lossy().into_owned()), + cause: None, + location: None, + } + } +} diff --git a/qemu/src/util/mod.rs b/qemu/src/util/mod.rs index be00d0c..e6078ac 100644 --- a/qemu/src/util/mod.rs +++ b/qemu/src/util/mod.rs @@ -1 +1,2 @@ +pub mod error; pub mod foreign; -- 2.45.2