// 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 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 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 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 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; } /// 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 + std::ops::Neg; /// 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 { 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(value: T) -> io::Result { 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 = Ok(0x8899_AABB); /// into_neg_errno(err) // panic /// # ; /// ``` pub fn into_neg_errno>(value: Result) -> 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 = Err(io::ErrorKind::PermissionDenied.into()); assert_eq!(into_neg_errno(err), -1); if cfg!(unix) { let os_err: io::Result = 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 = Err(io::ErrorKind::PermissionDenied.into()); assert_eq!(into_neg_errno(err), -1); if cfg!(unix) { let os_err: io::Result = 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 = Err(io::ErrorKind::PermissionDenied.into()); assert_eq!(into_neg_errno(err), -1); if cfg!(unix) { let os_err: io::Result = 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 = Err(io::ErrorKind::InvalidInput.into()); assert_eq!(into_neg_errno(err), -22); if cfg!(unix) { let os_err: io::Result = 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); } } }