This is an initial, minimal part of the chardev bindings. 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 and one would have to use the libc crate. Provide a small module that interprets QEMU's convention and does a best-effort translation to io::Error on Windows.
Signed-off-by: Paolo Bonzini <pbonz...@redhat.com> --- rust/qemu-api/meson.build | 1 + rust/qemu-api/src/assertions.rs | 28 ++++++ rust/qemu-api/src/errno.rs | 161 ++++++++++++++++++++++++++++++++ rust/qemu-api/src/lib.rs | 1 + rust/qemu-api/src/prelude.rs | 2 + 5 files changed, 193 insertions(+) create mode 100644 rust/qemu-api/src/errno.rs diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build index 26b8500e7d8..607c3010d1d 100644 --- a/rust/qemu-api/meson.build +++ b/rust/qemu-api/meson.build @@ -22,6 +22,7 @@ _qemu_api_rs = static_library( 'src/cell.rs', 'src/chardev.rs', 'src/c_str.rs', + 'src/errno.rs', 'src/irq.rs', 'src/memory.rs', 'src/module.rs', diff --git a/rust/qemu-api/src/assertions.rs b/rust/qemu-api/src/assertions.rs index fa1a18de6fe..104dec39774 100644 --- a/rust/qemu-api/src/assertions.rs +++ b/rust/qemu-api/src/assertions.rs @@ -92,3 +92,31 @@ fn types_must_be_equal<T, U>(_: T) }; }; } + +/// 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) + ); + }; +} diff --git a/rust/qemu-api/src/errno.rs b/rust/qemu-api/src/errno.rs new file mode 100644 index 00000000000..c697f9bef05 --- /dev/null +++ b/rust/qemu-api/src/errno.rs @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +//! Module to portably convert `errno` into [`io::Error`], and the +//! return value of functions returning `-errno` into [`io::Result`]. + +use std::io; +#[cfg(windows)] +use std::io::ErrorKind; + +/// An `errno` value that can be converted into an [`io::Error`] +pub struct Errno(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. For simplicity use ErrorKind and use +// the standard library's simple-minded mapping of ErrorKind to Error +// (`impl From<ErrorKind> for io::Error`). +// +// Since this is just for Windows, do not bother with using the libc +// crate or generating the constants from C. Just list here the +// constants that map to stable error kinds. +#[cfg(windows)] +mod libc { + pub const EPERM: u16 = 1; + pub const ENOENT: u16 = 2; + pub const EINTR: u16 = 4; + pub const EAGAIN: u16 = 11; + pub const ENOMEM: u16 = 12; + pub const EACCES: u16 = 13; + pub const EEXIST: u16 = 17; + pub const EINVAL: u16 = 22; + pub const EPIPE: u16 = 32; + pub const EADDRINUSE: u16 = 100; + pub const EADDRNOTAVAIL: u16 = 101; + pub const ECONNABORTED: u16 = 106; + pub const ECONNREFUSED: u16 = 107; + pub const ECONNRESET: u16 = 108; + pub const ENOTCONN: u16 = 126; + pub const ENOTSUP: u16 = 129; + pub const ETIMEDOUT: u16 = 138; + pub const EWOULDBLOCK: u16 = 140; +} + +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 Errno(errno) = value; + let error_kind = match errno { + libc::EPERM | libc::EACCES => ErrorKind::PermissionDenied, + libc::ENOENT => ErrorKind::NotFound, + libc::EINTR => ErrorKind::Interrupted, + // Note that on Windows we know these two are distinct. In general, + // it would not be possible to use "|". + libc::EAGAIN | libc::EWOULDBLOCK => ErrorKind::WouldBlock, + libc::ENOMEM => ErrorKind::OutOfMemory, + libc::EEXIST => ErrorKind::AlreadyExists, + libc::EINVAL => ErrorKind::InvalidInput, + libc::EPIPE => ErrorKind::BrokenPipe, + libc::EADDRINUSE => ErrorKind::AddrInUse, + libc::EADDRNOTAVAIL => ErrorKind::AddrNotAvailable, + libc::ECONNABORTED => ErrorKind::ConnectionAborted, + libc::ECONNREFUSED => ErrorKind::ConnectionRefused, + libc::ECONNRESET => ErrorKind::ConnectionReset, + libc::ENOTCONN => ErrorKind::NotConnected, + libc::ENOTSUP => ErrorKind::Unsupported, + libc::ETIMEDOUT => ErrorKind::TimedOut, + _ => ErrorKind::Other, + }; + error_kind.into() + } +} + +/// Convert an integer value into a [`Result`], where positive +/// values are turned into an `Ok` result and negative values are +/// interpreted as negated errno and turned into an `Err`. +pub trait GetErrno: Copy { + /// 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>; +} + +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); + +/// Convert an integer value into a [`io::Result`], where positive +/// values are turned into an `Ok` result and negative values are +/// interpreted as negated errno and turn into an `Err`. +/// +/// ``` +/// # use qemu_api::errno::into_io_result; +/// # use std::io::ErrorKind; +/// +/// let err = into_io_result(-1i32).unwrap_err(); // -EPERM +/// assert_eq!(err.kind(), ErrorKind::PermissionDenied); +/// ``` +pub fn into_io_result<T: GetErrno>(value: T) -> io::Result<T::Out> { + Ok(value.into_errno_result()?) +} + +#[cfg(test)] +mod tests { + use std::io::ErrorKind; + + use super::*; + use crate::assert_match; + + #[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_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_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); + } +} diff --git a/rust/qemu-api/src/lib.rs b/rust/qemu-api/src/lib.rs index ed1a8f9a2b4..05f38b51d30 100644 --- a/rust/qemu-api/src/lib.rs +++ b/rust/qemu-api/src/lib.rs @@ -19,6 +19,7 @@ pub mod callbacks; pub mod cell; pub mod chardev; +pub mod errno; pub mod irq; pub mod memory; pub mod module; diff --git a/rust/qemu-api/src/prelude.rs b/rust/qemu-api/src/prelude.rs index fbf0ee23e0b..634acf37a85 100644 --- a/rust/qemu-api/src/prelude.rs +++ b/rust/qemu-api/src/prelude.rs @@ -9,6 +9,8 @@ pub use crate::cell::BqlCell; pub use crate::cell::BqlRefCell; +pub use crate::errno; + pub use crate::qdev::DeviceMethods; pub use crate::qom::InterfaceType; -- 2.48.1