mirror of
https://github.com/Motorhead1991/qemu.git
synced 2025-07-27 04:13:53 -06:00
rust: qemu-api: add bindings to Error
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. One important difference is that these propagation methods *panic* if *errp is NULL, unlike error_propagate() which eats subsequent errors[1]. The reason for this is that in C you have an error_set*() call at the site where the error is created, and calls to error_propagate() are relatively rare. In Rust instead, even though these functions do "propagate" a qemu_api::Error into a C Error**, there is no error_setg() anywhere that could check for non-NULL errp and call abort(). error_propagate()'s behavior of ignoring subsequent errors is generally considered weird, and there would be a bigger risk of triggering it from Rust code. [1] This is actually a violation of the preconditions of error_propagate(), so it should not happen. But you never know... Reviewed-by: Zhao Liu <zhao1.liu@intel.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
This commit is contained in:
parent
e8fb9c91a3
commit
b4ff3cf34f
7 changed files with 341 additions and 0 deletions
|
@ -96,6 +96,11 @@ are missing:
|
||||||
architecture (VMState). Right now, VMState lacks type safety because
|
architecture (VMState). Right now, VMState lacks type safety because
|
||||||
it is hard to place the ``VMStateField`` definitions in traits.
|
it is hard to place the ``VMStateField`` definitions in traits.
|
||||||
|
|
||||||
|
* NUL-terminated file names with ``#[track_caller]`` are scheduled for
|
||||||
|
inclusion as ``#![feature(location_file_nul)]``, but it will be a while
|
||||||
|
before QEMU can use them. For now, there is special code in
|
||||||
|
``util/error.c`` to support non-NUL-terminated file names.
|
||||||
|
|
||||||
* associated const equality would be nice to have for some users of
|
* associated const equality would be nice to have for some users of
|
||||||
``callbacks::FnCall``, but is still experimental. ``ASSERT_IS_SOME``
|
``callbacks::FnCall``, but is still experimental. ``ASSERT_IS_SOME``
|
||||||
replaces it.
|
replaces it.
|
||||||
|
|
17
rust/Cargo.lock
generated
17
rust/Cargo.lock
generated
|
@ -2,6 +2,12 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.98"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arbitrary-int"
|
name = "arbitrary-int"
|
||||||
version = "1.2.7"
|
version = "1.2.7"
|
||||||
|
@ -44,6 +50,15 @@ version = "1.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
|
checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foreign"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "17ca1b5be8c9d320daf386f1809c7acc0cb09accbae795c2001953fa50585846"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hpet"
|
name = "hpet"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -114,6 +129,8 @@ dependencies = [
|
||||||
name = "qemu_api"
|
name = "qemu_api"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"foreign",
|
||||||
"libc",
|
"libc",
|
||||||
"qemu_api_macros",
|
"qemu_api_macros",
|
||||||
]
|
]
|
||||||
|
|
|
@ -67,6 +67,7 @@ missing_safety_doc = "deny"
|
||||||
mut_mut = "deny"
|
mut_mut = "deny"
|
||||||
needless_bitwise_bool = "deny"
|
needless_bitwise_bool = "deny"
|
||||||
needless_pass_by_ref_mut = "deny"
|
needless_pass_by_ref_mut = "deny"
|
||||||
|
needless_update = "deny"
|
||||||
no_effect_underscore_binding = "deny"
|
no_effect_underscore_binding = "deny"
|
||||||
option_option = "deny"
|
option_option = "deny"
|
||||||
or_fun_call = "deny"
|
or_fun_call = "deny"
|
||||||
|
|
|
@ -15,7 +15,9 @@ rust-version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
qemu_api_macros = { path = "../qemu-api-macros" }
|
qemu_api_macros = { path = "../qemu-api-macros" }
|
||||||
|
anyhow = "~1.0"
|
||||||
libc = "0.2.162"
|
libc = "0.2.162"
|
||||||
|
foreign = "~0.3.1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["debug_cell"]
|
default = ["debug_cell"]
|
||||||
|
|
|
@ -19,6 +19,7 @@ _qemu_api_rs = static_library(
|
||||||
'src/cell.rs',
|
'src/cell.rs',
|
||||||
'src/chardev.rs',
|
'src/chardev.rs',
|
||||||
'src/errno.rs',
|
'src/errno.rs',
|
||||||
|
'src/error.rs',
|
||||||
'src/irq.rs',
|
'src/irq.rs',
|
||||||
'src/memory.rs',
|
'src/memory.rs',
|
||||||
'src/module.rs',
|
'src/module.rs',
|
||||||
|
|
312
rust/qemu-api/src/error.rs
Normal file
312
rust/qemu-api/src/error.rs
Normal file
|
@ -0,0 +1,312 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
//! Error propagation for QEMU Rust code
|
||||||
|
//!
|
||||||
|
//! This module contains [`Error`], the bridge between Rust errors and
|
||||||
|
//! [`Result`](std::result::Result)s and QEMU's C [`Error`](bindings::Error)
|
||||||
|
//! struct.
|
||||||
|
//!
|
||||||
|
//! For FFI code, [`Error`] provides functions to simplify conversion between
|
||||||
|
//! the Rust ([`Result<>`](std::result::Result)) and C (`Error**`) conventions:
|
||||||
|
//!
|
||||||
|
//! * [`ok_or_propagate`](crate::Error::ok_or_propagate),
|
||||||
|
//! [`bool_or_propagate`](crate::Error::bool_or_propagate),
|
||||||
|
//! [`ptr_or_propagate`](crate::Error::ptr_or_propagate) can be used to build
|
||||||
|
//! a C return value while also propagating an error condition
|
||||||
|
//!
|
||||||
|
//! * [`err_or_else`](crate::Error::err_or_else) and
|
||||||
|
//! [`err_or_unit`](crate::Error::err_or_unit) can be used to build a `Result`
|
||||||
|
//!
|
||||||
|
//! This module is most commonly used at the boundary between C and Rust code;
|
||||||
|
//! other code will usually access it through the
|
||||||
|
//! [`qemu_api::Result`](crate::Result) type alias, and will use the
|
||||||
|
//! [`std::error::Error`] interface to let C errors participate in Rust's error
|
||||||
|
//! handling functionality.
|
||||||
|
//!
|
||||||
|
//! Rust code can also create use this module to create an error object that
|
||||||
|
//! will be passed up to C code, though in most cases this will be done
|
||||||
|
//! transparently through the `?` operator. Errors can be constructed from a
|
||||||
|
//! simple error string, from an [`anyhow::Error`] to pass any other Rust error
|
||||||
|
//! type up to C code, or from a combination of the two.
|
||||||
|
//!
|
||||||
|
//! The third case, corresponding to [`Error::with_error`], is the only one that
|
||||||
|
//! requires mentioning [`qemu_api::Error`](crate::Error) explicitly. Similar
|
||||||
|
//! to how QEMU's C code handles errno values, the string and the
|
||||||
|
//! `anyhow::Error` object will be concatenated with `:` as the separator.
|
||||||
|
|
||||||
|
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))
|
||||||
|
.expect("no message nor cause?")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
panic!("no message nor cause?");
|
||||||
|
}
|
||||||
|
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. This is
|
||||||
|
/// similar to the C API `error_propagate`, but it panics if `*errp`
|
||||||
|
/// is not `NULL`.
|
||||||
|
///
|
||||||
|
/// # 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.
|
||||||
|
///
|
||||||
|
/// See [`propagate`](Error::propagate) for more information.
|
||||||
|
#[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`. This is similar to the C API
|
||||||
|
/// `error_propagate`, but it panics if `*errp` is not `NULL`.
|
||||||
|
///
|
||||||
|
/// # 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.
|
||||||
|
///
|
||||||
|
/// See [`propagate`](Error::propagate) for more information.
|
||||||
|
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`
|
||||||
|
/// with the information container in `self` if `errp` is not NULL;
|
||||||
|
/// then consume it.
|
||||||
|
///
|
||||||
|
/// This is similar to the C API `error_propagate`, but it panics if
|
||||||
|
/// `*errp` is not `NULL`.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// `errp` must be a valid argument to `error_propagate`; it can be
|
||||||
|
/// `NULL` or it can point to any of:
|
||||||
|
/// * `error_abort`
|
||||||
|
/// * `error_fatal`
|
||||||
|
/// * a local variable of (C) type `Error *`
|
||||||
|
///
|
||||||
|
/// Typically `errp` 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) {
|
||||||
|
if errp.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: caller guarantees that errp and *errp are valid
|
||||||
|
unsafe {
|
||||||
|
assert_eq!(*errp, ptr::null_mut());
|
||||||
|
bindings::error_propagate(errp, self.clone_to_foreign_ptr());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
/// 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 point to a valid C [`struct
|
||||||
|
/// Error`](bindings::Error); 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ pub mod callbacks;
|
||||||
pub mod cell;
|
pub mod cell;
|
||||||
pub mod chardev;
|
pub mod chardev;
|
||||||
pub mod errno;
|
pub mod errno;
|
||||||
|
pub mod error;
|
||||||
pub mod irq;
|
pub mod irq;
|
||||||
pub mod memory;
|
pub mod memory;
|
||||||
pub mod module;
|
pub mod module;
|
||||||
|
@ -34,6 +35,8 @@ use std::{
|
||||||
ffi::c_void,
|
ffi::c_void,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub use error::{Error, Result};
|
||||||
|
|
||||||
#[cfg(HAVE_GLIB_WITH_ALIGNED_ALLOC)]
|
#[cfg(HAVE_GLIB_WITH_ALIGNED_ALLOC)]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
fn g_aligned_alloc0(
|
fn g_aligned_alloc0(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue