mirror of
https://github.com/Motorhead1991/qemu.git
synced 2025-08-02 07:13:54 -06:00
rust: add module to convert between success/-errno and io::Result
It is a common convention in QEMU to return a positive value in case of success, and a negated errno value in case of error. Unfortunately, using errno portably in Rust is a bit complicated; on Unix the errno values are supported natively by io::Error, but on Windows they are not; so, use the libc crate. This is a set of utility functions that are used by both chardev and block layer bindings. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
This commit is contained in:
parent
4cfe9edb1b
commit
8a420dd109
6 changed files with 381 additions and 0 deletions
|
@ -179,6 +179,7 @@ module status
|
||||||
``callbacks`` complete
|
``callbacks`` complete
|
||||||
``cell`` stable
|
``cell`` stable
|
||||||
``c_str`` complete
|
``c_str`` complete
|
||||||
|
``errno`` complete
|
||||||
``irq`` complete
|
``irq`` complete
|
||||||
``memory`` stable
|
``memory`` stable
|
||||||
``module`` complete
|
``module`` complete
|
||||||
|
|
|
@ -2,6 +2,8 @@ _qemu_api_cfg = run_command(rustc_args,
|
||||||
'--config-headers', config_host_h, '--features', files('Cargo.toml'),
|
'--config-headers', config_host_h, '--features', files('Cargo.toml'),
|
||||||
capture: true, check: true).stdout().strip().splitlines()
|
capture: true, check: true).stdout().strip().splitlines()
|
||||||
|
|
||||||
|
libc_dep = dependency('libc-0.2-rs')
|
||||||
|
|
||||||
# _qemu_api_cfg += ['--cfg', 'feature="allocator"']
|
# _qemu_api_cfg += ['--cfg', 'feature="allocator"']
|
||||||
if rustc.version().version_compare('>=1.77.0')
|
if rustc.version().version_compare('>=1.77.0')
|
||||||
_qemu_api_cfg += ['--cfg', 'has_offset_of']
|
_qemu_api_cfg += ['--cfg', 'has_offset_of']
|
||||||
|
@ -22,6 +24,7 @@ _qemu_api_rs = static_library(
|
||||||
'src/cell.rs',
|
'src/cell.rs',
|
||||||
'src/chardev.rs',
|
'src/chardev.rs',
|
||||||
'src/c_str.rs',
|
'src/c_str.rs',
|
||||||
|
'src/errno.rs',
|
||||||
'src/irq.rs',
|
'src/irq.rs',
|
||||||
'src/memory.rs',
|
'src/memory.rs',
|
||||||
'src/module.rs',
|
'src/module.rs',
|
||||||
|
@ -39,6 +42,7 @@ _qemu_api_rs = static_library(
|
||||||
override_options: ['rust_std=2021', 'build.rust_std=2021'],
|
override_options: ['rust_std=2021', 'build.rust_std=2021'],
|
||||||
rust_abi: 'rust',
|
rust_abi: 'rust',
|
||||||
rust_args: _qemu_api_cfg,
|
rust_args: _qemu_api_cfg,
|
||||||
|
dependencies: libc_dep,
|
||||||
)
|
)
|
||||||
|
|
||||||
rust.test('rust-qemu-api-tests', _qemu_api_rs,
|
rust.test('rust-qemu-api-tests', _qemu_api_rs,
|
||||||
|
|
|
@ -92,3 +92,31 @@ macro_rules! assert_field_type {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Assert that an expression matches a pattern. This can also be
|
||||||
|
/// useful to compare enums that do not implement `Eq`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use qemu_api::assert_match;
|
||||||
|
/// // JoinHandle does not implement `Eq`, therefore the result
|
||||||
|
/// // does not either.
|
||||||
|
/// let result: Result<std::thread::JoinHandle<()>, u32> = Err(42);
|
||||||
|
/// assert_match!(result, Err(42));
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! assert_match {
|
||||||
|
($a:expr, $b:pat) => {
|
||||||
|
assert!(
|
||||||
|
match $a {
|
||||||
|
$b => true,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
"{} = {:?} does not match {}",
|
||||||
|
stringify!($a),
|
||||||
|
$a,
|
||||||
|
stringify!($b)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
345
rust/qemu-api/src/errno.rs
Normal file
345
rust/qemu-api/src/errno.rs
Normal file
|
@ -0,0 +1,345 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
//! Utility functions to convert `errno` to and from
|
||||||
|
//! [`io::Error`]/[`io::Result`]
|
||||||
|
//!
|
||||||
|
//! QEMU C functions often have a "positive success/negative `errno`" calling
|
||||||
|
//! convention. This module provides functions to portably convert an integer
|
||||||
|
//! into an [`io::Result`] and back.
|
||||||
|
|
||||||
|
use std::{convert::TryFrom, io, io::ErrorKind};
|
||||||
|
|
||||||
|
/// An `errno` value that can be converted into an [`io::Error`]
|
||||||
|
pub struct Errno(pub u16);
|
||||||
|
|
||||||
|
// On Unix, from_raw_os_error takes an errno value and OS errors
|
||||||
|
// are printed using strerror. On Windows however it takes a
|
||||||
|
// GetLastError() value; therefore we need to convert errno values
|
||||||
|
// into io::Error by hand. This is the same mapping that the
|
||||||
|
// standard library uses to retrieve the kind of OS errors
|
||||||
|
// (`std::sys::pal::unix::decode_error_kind`).
|
||||||
|
impl From<Errno> for ErrorKind {
|
||||||
|
fn from(value: Errno) -> ErrorKind {
|
||||||
|
use ErrorKind::*;
|
||||||
|
let Errno(errno) = value;
|
||||||
|
match i32::from(errno) {
|
||||||
|
libc::EPERM | libc::EACCES => PermissionDenied,
|
||||||
|
libc::ENOENT => NotFound,
|
||||||
|
libc::EINTR => Interrupted,
|
||||||
|
x if x == libc::EAGAIN || x == libc::EWOULDBLOCK => WouldBlock,
|
||||||
|
libc::ENOMEM => OutOfMemory,
|
||||||
|
libc::EEXIST => AlreadyExists,
|
||||||
|
libc::EINVAL => InvalidInput,
|
||||||
|
libc::EPIPE => BrokenPipe,
|
||||||
|
libc::EADDRINUSE => AddrInUse,
|
||||||
|
libc::EADDRNOTAVAIL => AddrNotAvailable,
|
||||||
|
libc::ECONNABORTED => ConnectionAborted,
|
||||||
|
libc::ECONNREFUSED => ConnectionRefused,
|
||||||
|
libc::ECONNRESET => ConnectionReset,
|
||||||
|
libc::ENOTCONN => NotConnected,
|
||||||
|
libc::ENOTSUP => Unsupported,
|
||||||
|
libc::ETIMEDOUT => TimedOut,
|
||||||
|
_ => Other,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is used on Windows for all io::Errors, but also on Unix if the
|
||||||
|
// io::Error does not have a raw OS error. This is the reversed
|
||||||
|
// mapping of the above; EIO is returned for unknown ErrorKinds.
|
||||||
|
impl From<io::ErrorKind> for Errno {
|
||||||
|
fn from(value: io::ErrorKind) -> Errno {
|
||||||
|
use ErrorKind::*;
|
||||||
|
let errno = match value {
|
||||||
|
// can be both EPERM or EACCES :( pick one
|
||||||
|
PermissionDenied => libc::EPERM,
|
||||||
|
NotFound => libc::ENOENT,
|
||||||
|
Interrupted => libc::EINTR,
|
||||||
|
WouldBlock => libc::EAGAIN,
|
||||||
|
OutOfMemory => libc::ENOMEM,
|
||||||
|
AlreadyExists => libc::EEXIST,
|
||||||
|
InvalidInput => libc::EINVAL,
|
||||||
|
BrokenPipe => libc::EPIPE,
|
||||||
|
AddrInUse => libc::EADDRINUSE,
|
||||||
|
AddrNotAvailable => libc::EADDRNOTAVAIL,
|
||||||
|
ConnectionAborted => libc::ECONNABORTED,
|
||||||
|
ConnectionRefused => libc::ECONNREFUSED,
|
||||||
|
ConnectionReset => libc::ECONNRESET,
|
||||||
|
NotConnected => libc::ENOTCONN,
|
||||||
|
Unsupported => libc::ENOTSUP,
|
||||||
|
TimedOut => libc::ETIMEDOUT,
|
||||||
|
_ => libc::EIO,
|
||||||
|
};
|
||||||
|
Errno(errno as u16)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Errno> for io::Error {
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn from(value: Errno) -> io::Error {
|
||||||
|
let Errno(errno) = value;
|
||||||
|
io::Error::from_raw_os_error(errno.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn from(value: Errno) -> io::Error {
|
||||||
|
let error_kind: ErrorKind = value.into();
|
||||||
|
error_kind.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<io::Error> for Errno {
|
||||||
|
fn from(value: io::Error) -> Errno {
|
||||||
|
if cfg!(unix) {
|
||||||
|
if let Some(errno) = value.raw_os_error() {
|
||||||
|
return Errno(u16::try_from(errno).unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value.kind().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internal traits; used to enable [`into_io_result`] and [`into_neg_errno`]
|
||||||
|
/// for the "right" set of types.
|
||||||
|
mod traits {
|
||||||
|
use super::Errno;
|
||||||
|
|
||||||
|
/// A signed type that can be converted into an
|
||||||
|
/// [`io::Result`](std::io::Result)
|
||||||
|
pub trait GetErrno {
|
||||||
|
/// Unsigned variant of `Self`, used as the type for the `Ok` case.
|
||||||
|
type Out;
|
||||||
|
|
||||||
|
/// Return `Ok(self)` if positive, `Err(Errno(-self))` if negative
|
||||||
|
fn into_errno_result(self) -> Result<Self::Out, Errno>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A type that can be taken out of an [`io::Result`](std::io::Result) and
|
||||||
|
/// converted into "positive success/negative `errno`" convention.
|
||||||
|
pub trait MergeErrno {
|
||||||
|
/// Signed variant of `Self`, used as the return type of
|
||||||
|
/// [`into_neg_errno`](super::into_neg_errno).
|
||||||
|
type Out: From<u16> + std::ops::Neg<Output = Self::Out>;
|
||||||
|
|
||||||
|
/// Return `self`, asserting that it is in range
|
||||||
|
fn map_ok(self) -> Self::Out;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! get_errno {
|
||||||
|
($t:ty, $out:ty) => {
|
||||||
|
impl GetErrno for $t {
|
||||||
|
type Out = $out;
|
||||||
|
fn into_errno_result(self) -> Result<Self::Out, Errno> {
|
||||||
|
match self {
|
||||||
|
0.. => Ok(self as $out),
|
||||||
|
-65535..=-1 => Err(Errno(-self as u16)),
|
||||||
|
_ => panic!("{self} is not a negative errno"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
get_errno!(i32, u32);
|
||||||
|
get_errno!(i64, u64);
|
||||||
|
get_errno!(isize, usize);
|
||||||
|
|
||||||
|
macro_rules! merge_errno {
|
||||||
|
($t:ty, $out:ty) => {
|
||||||
|
impl MergeErrno for $t {
|
||||||
|
type Out = $out;
|
||||||
|
fn map_ok(self) -> Self::Out {
|
||||||
|
self.try_into().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
merge_errno!(u8, i32);
|
||||||
|
merge_errno!(u16, i32);
|
||||||
|
merge_errno!(u32, i32);
|
||||||
|
merge_errno!(u64, i64);
|
||||||
|
|
||||||
|
impl MergeErrno for () {
|
||||||
|
type Out = i32;
|
||||||
|
fn map_ok(self) -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use traits::{GetErrno, MergeErrno};
|
||||||
|
|
||||||
|
/// Convert an integer value into a [`io::Result`].
|
||||||
|
///
|
||||||
|
/// Positive values are turned into an `Ok` result; negative values
|
||||||
|
/// are interpreted as negated `errno` and turned into an `Err`.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use qemu_api::errno::into_io_result;
|
||||||
|
/// # use std::io::ErrorKind;
|
||||||
|
/// let ok = into_io_result(1i32).unwrap();
|
||||||
|
/// assert_eq!(ok, 1u32);
|
||||||
|
///
|
||||||
|
/// let err = into_io_result(-1i32).unwrap_err(); // -EPERM
|
||||||
|
/// assert_eq!(err.kind(), ErrorKind::PermissionDenied);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Since the result is an unsigned integer, negative values must
|
||||||
|
/// be close to 0; values that are too far away are considered
|
||||||
|
/// likely overflows and will panic:
|
||||||
|
///
|
||||||
|
/// ```should_panic
|
||||||
|
/// # use qemu_api::errno::into_io_result;
|
||||||
|
/// # #[allow(dead_code)]
|
||||||
|
/// let err = into_io_result(-0x1234_5678i32); // panic
|
||||||
|
/// ```
|
||||||
|
pub fn into_io_result<T: GetErrno>(value: T) -> io::Result<T::Out> {
|
||||||
|
value.into_errno_result().map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a [`Result`] into an integer value, using negative `errno`
|
||||||
|
/// values to report errors.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use qemu_api::errno::into_neg_errno;
|
||||||
|
/// # use std::io::{self, ErrorKind};
|
||||||
|
/// let ok: io::Result<()> = Ok(());
|
||||||
|
/// assert_eq!(into_neg_errno(ok), 0);
|
||||||
|
///
|
||||||
|
/// let err: io::Result<()> = Err(ErrorKind::InvalidInput.into());
|
||||||
|
/// assert_eq!(into_neg_errno(err), -22); // -EINVAL
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Since this module also provides the ability to convert [`io::Error`]
|
||||||
|
/// to an `errno` value, [`io::Result`] is the most commonly used type
|
||||||
|
/// for the argument of this function:
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Since the result is a signed integer, integer `Ok` values must remain
|
||||||
|
/// positive:
|
||||||
|
///
|
||||||
|
/// ```should_panic
|
||||||
|
/// # use qemu_api::errno::into_neg_errno;
|
||||||
|
/// # use std::io;
|
||||||
|
/// let err: io::Result<u32> = Ok(0x8899_AABB);
|
||||||
|
/// into_neg_errno(err) // panic
|
||||||
|
/// # ;
|
||||||
|
/// ```
|
||||||
|
pub fn into_neg_errno<T: MergeErrno, E: Into<Errno>>(value: Result<T, E>) -> T::Out {
|
||||||
|
match value {
|
||||||
|
Ok(x) => x.map_ok(),
|
||||||
|
Err(err) => -T::Out::from(err.into().0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::io::ErrorKind;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::assert_match;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_from_u8() {
|
||||||
|
let ok: io::Result<_> = Ok(42u8);
|
||||||
|
assert_eq!(into_neg_errno(ok), 42);
|
||||||
|
|
||||||
|
let err: io::Result<u8> = Err(io::ErrorKind::PermissionDenied.into());
|
||||||
|
assert_eq!(into_neg_errno(err), -1);
|
||||||
|
|
||||||
|
if cfg!(unix) {
|
||||||
|
let os_err: io::Result<u8> = Err(io::Error::from_raw_os_error(10));
|
||||||
|
assert_eq!(into_neg_errno(os_err), -10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_from_u16() {
|
||||||
|
let ok: io::Result<_> = Ok(1234u16);
|
||||||
|
assert_eq!(into_neg_errno(ok), 1234);
|
||||||
|
|
||||||
|
let err: io::Result<u16> = Err(io::ErrorKind::PermissionDenied.into());
|
||||||
|
assert_eq!(into_neg_errno(err), -1);
|
||||||
|
|
||||||
|
if cfg!(unix) {
|
||||||
|
let os_err: io::Result<u16> = Err(io::Error::from_raw_os_error(10));
|
||||||
|
assert_eq!(into_neg_errno(os_err), -10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_i32() {
|
||||||
|
assert_match!(into_io_result(1234i32), Ok(1234));
|
||||||
|
|
||||||
|
let err = into_io_result(-1i32).unwrap_err();
|
||||||
|
#[cfg(unix)]
|
||||||
|
assert_match!(err.raw_os_error(), Some(1));
|
||||||
|
assert_match!(err.kind(), ErrorKind::PermissionDenied);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_from_u32() {
|
||||||
|
let ok: io::Result<_> = Ok(1234u32);
|
||||||
|
assert_eq!(into_neg_errno(ok), 1234);
|
||||||
|
|
||||||
|
let err: io::Result<u32> = Err(io::ErrorKind::PermissionDenied.into());
|
||||||
|
assert_eq!(into_neg_errno(err), -1);
|
||||||
|
|
||||||
|
if cfg!(unix) {
|
||||||
|
let os_err: io::Result<u32> = Err(io::Error::from_raw_os_error(10));
|
||||||
|
assert_eq!(into_neg_errno(os_err), -10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_i64() {
|
||||||
|
assert_match!(into_io_result(1234i64), Ok(1234));
|
||||||
|
|
||||||
|
let err = into_io_result(-22i64).unwrap_err();
|
||||||
|
#[cfg(unix)]
|
||||||
|
assert_match!(err.raw_os_error(), Some(22));
|
||||||
|
assert_match!(err.kind(), ErrorKind::InvalidInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_from_u64() {
|
||||||
|
let ok: io::Result<_> = Ok(1234u64);
|
||||||
|
assert_eq!(into_neg_errno(ok), 1234);
|
||||||
|
|
||||||
|
let err: io::Result<u64> = Err(io::ErrorKind::InvalidInput.into());
|
||||||
|
assert_eq!(into_neg_errno(err), -22);
|
||||||
|
|
||||||
|
if cfg!(unix) {
|
||||||
|
let os_err: io::Result<u64> = Err(io::Error::from_raw_os_error(6));
|
||||||
|
assert_eq!(into_neg_errno(os_err), -6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_isize() {
|
||||||
|
assert_match!(into_io_result(1234isize), Ok(1234));
|
||||||
|
|
||||||
|
let err = into_io_result(-4isize).unwrap_err();
|
||||||
|
#[cfg(unix)]
|
||||||
|
assert_match!(err.raw_os_error(), Some(4));
|
||||||
|
assert_match!(err.kind(), ErrorKind::Interrupted);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_from_unit() {
|
||||||
|
let ok: io::Result<_> = Ok(());
|
||||||
|
assert_eq!(into_neg_errno(ok), 0);
|
||||||
|
|
||||||
|
let err: io::Result<()> = Err(io::ErrorKind::OutOfMemory.into());
|
||||||
|
assert_eq!(into_neg_errno(err), -12);
|
||||||
|
|
||||||
|
if cfg!(unix) {
|
||||||
|
let os_err: io::Result<()> = Err(io::Error::from_raw_os_error(2));
|
||||||
|
assert_eq!(into_neg_errno(os_err), -2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ pub mod c_str;
|
||||||
pub mod callbacks;
|
pub mod callbacks;
|
||||||
pub mod cell;
|
pub mod cell;
|
||||||
pub mod chardev;
|
pub mod chardev;
|
||||||
|
pub mod errno;
|
||||||
pub mod irq;
|
pub mod irq;
|
||||||
pub mod memory;
|
pub mod memory;
|
||||||
pub mod module;
|
pub mod module;
|
||||||
|
|
|
@ -9,6 +9,8 @@ pub use crate::bitops::IntegerExt;
|
||||||
pub use crate::cell::BqlCell;
|
pub use crate::cell::BqlCell;
|
||||||
pub use crate::cell::BqlRefCell;
|
pub use crate::cell::BqlRefCell;
|
||||||
|
|
||||||
|
pub use crate::errno;
|
||||||
|
|
||||||
pub use crate::qdev::DeviceMethods;
|
pub use crate::qdev::DeviceMethods;
|
||||||
|
|
||||||
pub use crate::qom::InterfaceType;
|
pub use crate::qom::InterfaceType;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue