Paolo Bonzini <pbonz...@redhat.com> writes: > Provide an implementation of std::error::Error that bridges the Rust > anyhow::Error and std::panic::Location types with QEMU's Error*. > It also has several utility methods, analogous to error_propagate(), > that convert a Result into a return value + Error** pair. > > Signed-off-by: Paolo Bonzini <pbonz...@redhat.com>
[...] > diff --git a/rust/qemu-api/src/error.rs b/rust/qemu-api/src/error.rs > new file mode 100644 > index 00000000000..0bdd413a0a2 > --- /dev/null > +++ b/rust/qemu-api/src/error.rs > @@ -0,0 +1,299 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > + > +//! Error propagation for QEMU Rust code > +//! > +//! In QEMU, an `Error` usually consists of a message and an errno value. Uh, it actually consists of a message and an ErrorClass value. However, use of anything but ERROR_CLASS_GENERIC_ERROR is strongly discouraged. Historical reasons... You completely ignore ErrorClass in your Rust interface. I approve. There are convenience functions that accept an errno, but they don't store the errno in the Error struct, they append ": " and strerror(errno) to the message. Same for Windows GetLastError() values. > +//! In this wrapper, the errno value is replaced by an [`anyhow::Error`] I'm not sure the anyhow::Error replaces anything. It's simply the bridge to idiomatic Rust errors. > +//! so that it is easy to pass any other Rust error type up to C code. This is true. > +//! Note that the backtrace that is provided by `anyhow` is not used yet, > +//! only the message ends up in the QEMU `Error*`. > +//! > +//! The message part can be used to clarify the inner error, similar to > +//! `error_prepend`, and of course to describe an erroneous condition that Clarify you're talking about C error_prepend() here? > +//! does not come from another [`Error`](std::error::Error) (for example an > +//! invalid argument). > +//! > +//! On top of implementing [`std::error::Error`], [`Error`] provides > functions Suggest to wrap comments a bit earlier. > +//! to simplify conversion between [`Result<>`](std::result::Result) and > +//! C `Error**` conventions. In particular: > +//! > +//! * [`ok_or_propagate`](qemu_api::Error::ok_or_propagate), > +//! [`bool_or_propagate`](qemu_api::Error::bool_or_propagate), > +//! [`ptr_or_propagate`](qemu_api::Error::ptr_or_propagate) can be used to > +//! build a C return value while also propagating an error condition > +//! > +//! * [`err_or_else`](qemu_api::Error::err_or_else) and > +//! [`err_or_unit`](qemu_api::Error::err_or_unit) can be used to build a > +//! `Result` > +//! > +//! While these facilities are useful at the boundary between C and Rust > code, > +//! other Rust code need not care about the existence of this module; it can > +//! just use the [`qemu_api::Result`] type alias and rely on the `?` > operator as > +//! usual. > +//! > +//! @author Paolo Bonzini > + > +use std::{ > + borrow::Cow, > + ffi::{c_char, c_int, c_void, CStr}, > + fmt::{self, Display}, > + panic, ptr, > +}; > + > +use foreign::{prelude::*, OwnedPointer}; > + > +use crate::bindings; > + > +pub type Result<T> = std::result::Result<T, Error>; > + > +#[derive(Debug)] > +pub struct Error { > + msg: Option<Cow<'static, str>>, > + /// Appends the print string of the error to the msg if not None > + cause: Option<anyhow::Error>, > + file: &'static str, > + line: u32, > +} > + > +impl std::error::Error for Error { > + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { > + self.cause.as_ref().map(AsRef::as_ref) > + } > + > + #[allow(deprecated)] > + fn description(&self) -> &str { > + self.msg > + .as_deref() > + .or_else(|| > self.cause.as_deref().map(std::error::Error::description)) > + .unwrap_or("unknown error") Can "unknown error" still happen now you dropped the Default trait? > + } > +} > + > +impl Display for Error { > + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { > + let mut prefix = ""; > + if let Some(ref msg) = self.msg { > + write!(f, "{msg}")?; > + prefix = ": "; > + } > + if let Some(ref cause) = self.cause { > + write!(f, "{prefix}{cause}")?; > + } else if prefix.is_empty() { > + f.write_str("unknown error")?; Can we still get here now you dropped the Default trait? > + } > + Ok(()) > + } > +} > + > +impl From<String> for Error { > + #[track_caller] > + fn from(msg: String) -> Self { > + let location = panic::Location::caller(); > + Error { > + msg: Some(Cow::Owned(msg)), > + cause: None, > + file: location.file(), > + line: location.line(), > + } > + } > +} > + > +impl From<&'static str> for Error { > + #[track_caller] > + fn from(msg: &'static str) -> Self { > + let location = panic::Location::caller(); > + Error { > + msg: Some(Cow::Borrowed(msg)), > + cause: None, > + file: location.file(), > + line: location.line(), > + } > + } > +} > + > +impl From<anyhow::Error> for Error { > + #[track_caller] > + fn from(error: anyhow::Error) -> Self { > + let location = panic::Location::caller(); > + Error { > + msg: None, > + cause: Some(error), > + file: location.file(), > + line: location.line(), > + } > + } > +} > + > +impl Error { > + /// Create a new error, prepending `msg` to the > + /// description of `cause` > + #[track_caller] > + pub fn with_error(msg: impl Into<Cow<'static, str>>, cause: impl > Into<anyhow::Error>) -> Self { > + let location = panic::Location::caller(); > + Error { > + msg: Some(msg.into()), > + cause: Some(cause.into()), > + file: location.file(), > + line: location.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 a valid argument to `error_propagate`; > + /// typically it is received from C code and need not be > + /// checked further at the Rust↔C boundary. > + pub unsafe fn bool_or_propagate(result: Result<()>, errp: *mut *mut > bindings::Error) -> bool { > + // SAFETY: caller guarantees errp is valid > + unsafe { 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 a valid argument to `error_propagate`; > + /// typically it is received from C code and need not be > + /// checked further at the Rust↔C boundary. > + #[must_use] > + pub unsafe fn ptr_or_propagate<T: CloneToForeign>( > + result: Result<T>, > + errp: *mut *mut bindings::Error, > + ) -> *mut T::Foreign { > + // SAFETY: caller guarantees errp is valid > + unsafe { Self::ok_or_propagate(result, errp) }.clone_to_foreign_ptr() > + } > + > + /// Consume a result in the same way as `self.ok()`, but also propagate > + /// a possible error into `errp`, like the C API `error_propagate` > + /// would do. > + /// > + /// # Safety > + /// > + /// `errp` must be a valid argument to `error_propagate`; > + /// typically it is received from C code and need not be > + /// checked further at the Rust↔C boundary. > + pub unsafe fn ok_or_propagate<T>( > + result: Result<T>, > + errp: *mut *mut bindings::Error, > + ) -> Option<T> { > + result.map_err(|err| unsafe { err.propagate(errp) }).ok() > + } > + > + /// Equivalent of the C function `error_propagate`. Fill `*errp` Uh, is it? Let's see... > + /// with the information container in `self` if `errp` is not NULL; > + /// then consume it. > + /// > + /// # Safety > + /// > + /// `errp` must be a valid argument to `error_propagate`; Reminder for later: the valid @errp arguments for C error_propagate() are * NULL * &error_abort * &error_fatal * Address of some Error * variable containing NULL * Address of some Error * variable containing non-NULL The last one is *not* valid with error_setg(). > + /// typically it is received from C code and need not be > + /// checked further at the Rust↔C boundary. > + pub unsafe fn propagate(self, errp: *mut *mut bindings::Error) { Reminder, just to avoid confusion: C error_propagate() has the arguments in the opposite order. > + if errp.is_null() { > + return; > + } > + > + let err = self.clone_to_foreign_ptr(); > + > + // SAFETY: caller guarantees errp is valid > + unsafe { > + errp.write(err); > + } > + } In C, we have two subtly different ways to store into some Error **errp argument: error_setg() and error_propagate(). Their obvious difference is that error_setg() creates the Error object to store, while error_propagate() stores an existing Error object if any, else does nothing. Their unobvious difference is behavior when the destination already contains an Error. With error_setg(), this must not happen. error_propagate() instead throws away the new error. This permits "first one wins" error accumulation. Design mistake if you ask me. Your Rust propagate() also stores an existing bindings::Error. Note that "else does nothing" doesn't apply, because we always have an existing error object here, namely @self. In the error_propagate() camp so far. Let's examine the other aspect: how exactly "storing" behaves. error_setg() according to its contract: If @errp is NULL, the error is ignored. [...] If @errp is &error_abort, print a suitable message and abort(). If @errp is &error_fatal, print a suitable message and exit(1). If @errp is anything else, *@errp must be NULL. error_propagate() according to its contract: [...] if @dst_errp is NULL, errors are being ignored. Free the error object. Else, if @dst_errp is &error_abort, print a suitable message and abort(). Else, if @dst_errp is &error_fatal, print a suitable message and exit(1). Else, if @dst_errp already contains an error, ignore this one: free the error object. Else, move the error object from @local_err to *@dst_errp. The second to last clause is where its storing differs from error_setg(). What does errp.write(err) do? I *guess* it simply stores @err in @errp. Matches neither behavior. If that's true, then passing &error_abort or &error_fatal to Rust does not work, and neither does error accumulation. Not equivalent of C error_propagate(). Is "propagate" semantics what you want here? If not, use another name. > + > + /// Convert a C `Error*` into a Rust `Result`, using > + /// `Ok(())` if `c_error` is NULL. Free the `Error*`. > + /// > + /// # Safety > + /// > + /// `c_error` must be `NULL` or valid; typically it was initialized Double-checking: "valid" means it points to struct Error. > + /// with `ptr::null_mut()` and passed by reference to a C function. > + pub unsafe fn err_or_unit(c_error: *mut bindings::Error) -> Result<()> { > + // SAFETY: caller guarantees c_error is valid > + unsafe { Self::err_or_else(c_error, || ()) } > + } > + > + /// Convert a C `Error*` into a Rust `Result`, calling `f()` to > + /// obtain an `Ok` value if `c_error` is NULL. Free the `Error*`. > + /// > + /// # Safety > + /// > + /// `c_error` must be `NULL` or valid; typically it was initialized > + /// with `ptr::null_mut()` and passed by reference to a C function. > + pub unsafe fn err_or_else<T, F: FnOnce() -> T>( > + c_error: *mut bindings::Error, > + f: F, > + ) -> Result<T> { > + // SAFETY: caller guarantees c_error is valid > + let err = unsafe { Option::<Self>::from_foreign(c_error) }; > + match err { > + None => Ok(f()), > + Some(err) => Err(err), > + } > + } > +} > + > +impl FreeForeign for Error { > + type Foreign = bindings::Error; > + > + unsafe fn free_foreign(p: *mut bindings::Error) { > + // SAFETY: caller guarantees p is valid > + unsafe { > + bindings::error_free(p); > + } > + } > +} > + > +impl CloneToForeign for Error { > + fn clone_to_foreign(&self) -> OwnedPointer<Self> { > + // SAFETY: all arguments are controlled by this function > + unsafe { > + let err: *mut c_void = > libc::malloc(std::mem::size_of::<bindings::Error>()); > + let err: &mut bindings::Error = &mut *err.cast(); > + *err = bindings::Error { > + msg: format!("{self}").clone_to_foreign_ptr(), > + err_class: bindings::ERROR_CLASS_GENERIC_ERROR, > + src_len: self.file.len() as c_int, > + src: self.file.as_ptr().cast::<c_char>(), > + line: self.line as c_int, > + func: ptr::null_mut(), > + hint: ptr::null_mut(), > + }; > + OwnedPointer::new(err) > + } > + } > +} > + > +impl FromForeign for Error { > + unsafe fn cloned_from_foreign(c_error: *const bindings::Error) -> Self { > + // SAFETY: caller guarantees c_error is valid > + unsafe { > + let error = &*c_error; > + let file = if error.src_len < 0 { > + // NUL-terminated > + CStr::from_ptr(error.src).to_str() > + } else { > + // Can become str::from_utf8 with Rust 1.87.0 > + std::str::from_utf8(std::slice::from_raw_parts( > + &*error.src.cast::<u8>(), > + error.src_len as usize, > + )) > + }; > + > + Error { > + msg: FromForeign::cloned_from_foreign(error.msg), > + cause: None, > + file: file.unwrap(), > + line: error.line as u32, > + } > + } > + } > +} > diff --git a/rust/qemu-api/src/lib.rs b/rust/qemu-api/src/lib.rs > index 234a94e3c29..93902fc94bc 100644 > --- a/rust/qemu-api/src/lib.rs > +++ b/rust/qemu-api/src/lib.rs > @@ -19,6 +19,7 @@ > pub mod cell; > pub mod chardev; > pub mod errno; > +pub mod error; > pub mod irq; > pub mod memory; > pub mod module; > @@ -34,6 +35,8 @@ > ffi::c_void, > }; > > +pub use error::{Error, Result}; > + > #[cfg(HAVE_GLIB_WITH_ALIGNED_ALLOC)] > extern "C" { > fn g_aligned_alloc0(