From 504aa854e1860fb736ca1575f04f505c494b5d93 Mon Sep 17 00:00:00 2001 From: Arlie Davis Date: Sat, 8 Jun 2024 14:19:27 -0700 Subject: [PATCH] allow windows-result to work on linux --- crates/libs/result/src/bindings.rs | 4 - crates/libs/result/src/error.rs | 250 +++++++++++++++++++++----- crates/libs/result/src/hresult.rs | 85 +++++---- crates/libs/result/src/lib.rs | 7 + crates/libs/result/tests/bindings.txt | 4 - 5 files changed, 261 insertions(+), 89 deletions(-) diff --git a/crates/libs/result/src/bindings.rs b/crates/libs/result/src/bindings.rs index e7b0d64bfc8..99106f48b05 100644 --- a/crates/libs/result/src/bindings.rs +++ b/crates/libs/result/src/bindings.rs @@ -17,10 +17,6 @@ windows_targets::link!("oleaut32.dll" "system" fn SysFreeString(bstrstring : BST windows_targets::link!("oleaut32.dll" "system" fn SysStringLen(pbstr : BSTR) -> u32); pub type BOOL = i32; pub type BSTR = *const u16; -pub const ERROR_INVALID_DATA: WIN32_ERROR = 13u32; -pub const ERROR_NO_UNICODE_TRANSLATION: WIN32_ERROR = 1113u32; -pub const E_INVALIDARG: HRESULT = 0x80070057_u32 as _; -pub const E_UNEXPECTED: HRESULT = 0x8000FFFF_u32 as _; pub const FORMAT_MESSAGE_ALLOCATE_BUFFER: FORMAT_MESSAGE_OPTIONS = 256u32; pub const FORMAT_MESSAGE_FROM_HMODULE: FORMAT_MESSAGE_OPTIONS = 2048u32; pub const FORMAT_MESSAGE_FROM_SYSTEM: FORMAT_MESSAGE_OPTIONS = 4096u32; diff --git a/crates/libs/result/src/error.rs b/crates/libs/result/src/error.rs index ed28d975d2d..70faed66e7e 100644 --- a/crates/libs/result/src/error.rs +++ b/crates/libs/result/src/error.rs @@ -1,10 +1,57 @@ use super::*; +use crate::HRESULT; use core::ffi::c_void; +#[allow(dead_code)] // for #[cfg(not_yet_stable)] +mod winerror { + use crate::HRESULT; + + // We define these here for non-Windows platforms. + // It's just as easy to use them on Windows, too. + + // Keep sorted by numeric value + pub const E_OUTOFMEMORY: HRESULT = HRESULT::from_u32(0x8007000E); + pub const E_UNEXPECTED: HRESULT = HRESULT::from_u32(0x8000FFFF); + pub const E_FAIL: HRESULT = HRESULT::from_u32(0x80004005); + pub const E_INVALIDARG: HRESULT = HRESULT::from_u32(0x80070057); + + // Keep sorted by numeric value + pub const ERROR_ACCESS_DENIED: u32 = 5; + pub const ERROR_INVALID_DATA: u32 = 13; + pub const ERROR_HANDLE_EOF: u32 = 38; + pub const ERROR_HANDLE_DISK_FULL: u32 = 39; + pub const ERROR_NOT_SUPPORTED: u32 = 50; + pub const ERROR_BROKEN_PIPE: u32 = 109; + pub const ERROR_NO_UNICODE_TRANSLATION: u32 = 1113; + pub const ERROR_POSSIBLE_DEADLOCK: u32 = 1131; + pub const ERROR_NOT_FOUND: u32 = 1168; + pub const ERROR_TIMEOUT: u32 = 1460; + pub const ERROR_DISK_QUOTA_EXCEEDED: u32 = 1295; + pub const ERROR_SEEK_ON_DEVICE: u32 = 132; + pub const ERROR_DIR_NOT_EMPTY: u32 = 145; + pub const ERROR_ALREADY_EXISTS: u32 = 183; + pub const ERROR_DIRECTORY_NOT_SUPPORTED: u32 = 336; + pub const ERROR_FILE_READ_ONLY: u32 = 6009; + pub const WSAECONNREFUSED: u32 = 10061; + pub const WSAECONNRESET: u32 = 10054; + pub const WSAECONNABORTED: u32 = 10053; + pub const WSAEHOSTUNREACH: u32 = 10065; + pub const WSAENETUNREACH: u32 = 10051; + pub const WSAENOTCONN: u32 = 10057; + pub const WSAEADDRINUSE: u32 = 10048; + pub const WSAEADDRNOTAVAIL: u32 = 10049; + pub const WSAENETDOWN: u32 = 10050; + pub const WSAEINTR: u32 = 10004; + pub const WSAEWOULDBLOCK: u32 = 10035; +} + +use winerror::{ERROR_INVALID_DATA, ERROR_NO_UNICODE_TRANSLATION, E_INVALIDARG, *}; + /// An error object consists of both an error code as well as detailed error information for debugging. #[derive(Clone, PartialEq, Eq)] pub struct Error { code: HRESULT, + #[cfg(windows)] info: Option, } @@ -13,6 +60,7 @@ impl Error { pub const fn empty() -> Self { Self { code: HRESULT(0), + #[cfg(windows)] info: None, } } @@ -20,24 +68,36 @@ impl Error { /// Creates a new error object, capturing the stack and other information about the /// point of failure. pub fn new>(code: HRESULT, message: T) -> Self { - let message: Vec<_> = message.as_ref().encode_utf16().collect(); - - if message.is_empty() { - Self::from_hresult(code) - } else { - unsafe { - RoOriginateErrorW(code.0, message.len() as u32, message.as_ptr()); + #[cfg(windows)] + { + let message: Vec<_> = message.as_ref().encode_utf16().collect(); + if message.is_empty() { + Self::from_hresult(code) + } else { + unsafe { + RoOriginateErrorW(code.0, message.len() as u32, message.as_ptr()); + } + code.into() } - code.into() + } + #[cfg(not(windows))] + { + let _ = message; + Self::from_hresult(code) } } /// Creates a new error object with an error code, but without additional error information. pub fn from_hresult(code: HRESULT) -> Self { - Self { code, info: None } + Self { + code, + #[cfg(windows)] + info: None, + } } /// Creates a new `Error` from the Win32 error code returned by `GetLastError()`. + #[cfg(windows)] pub fn from_win32() -> Self { Self { code: HRESULT::from_win32(unsafe { GetLastError() }), @@ -52,42 +112,45 @@ impl Error { /// The error message describing the error. pub fn message(&self) -> String { - if let Some(info) = &self.info { - let mut message = BasicString::default(); + #[cfg(windows)] + { + if let Some(info) = &self.info { + let mut message = BasicString::default(); - // First attempt to retrieve the restricted error information. - if let Some(info) = info.cast(&IID_IRestrictedErrorInfo) { - let mut fallback = BasicString::default(); - let mut code = 0; + // First attempt to retrieve the restricted error information. + if let Some(info) = info.cast(&IID_IRestrictedErrorInfo) { + let mut fallback = BasicString::default(); + let mut code = 0; - unsafe { - com_call!( - IRestrictedErrorInfo_Vtbl, - info.GetErrorDetails( - &mut fallback as *mut _ as _, - &mut code, - &mut message as *mut _ as _, - &mut BasicString::default() as *mut _ as _ - ) - ); + unsafe { + com_call!( + IRestrictedErrorInfo_Vtbl, + info.GetErrorDetails( + &mut fallback as *mut _ as _, + &mut code, + &mut message as *mut _ as _, + &mut BasicString::default() as *mut _ as _ + ) + ); + } + + if message.is_empty() { + message = fallback + }; } + // Next attempt to retrieve the regular error information. if message.is_empty() { - message = fallback - }; - } - - // Next attempt to retrieve the regular error information. - if message.is_empty() { - unsafe { - com_call!( - IErrorInfo_Vtbl, - info.GetDescription(&mut message as *mut _ as _) - ); + unsafe { + com_call!( + IErrorInfo_Vtbl, + info.GetDescription(&mut message as *mut _ as _) + ); + } } - } - return String::from_utf16_lossy(wide_trim_end(message.as_wide())); + return String::from_utf16_lossy(wide_trim_end(message.as_wide())); + } } // Otherwise fallback to a generic error code description. @@ -95,6 +158,7 @@ impl Error { } /// The error object describing the error. + #[cfg(windows)] pub fn as_ptr(&self) -> *mut c_void { self.info .as_ref() @@ -109,9 +173,12 @@ unsafe impl Sync for Error {} impl From for HRESULT { fn from(error: Error) -> Self { - if let Some(info) = error.info { - unsafe { - SetErrorInfo(0, info.as_raw()); + #[cfg(windows)] + { + if let Some(info) = error.info { + unsafe { + SetErrorInfo(0, info.as_raw()); + } } } error.code @@ -119,10 +186,18 @@ impl From for HRESULT { } impl From for Error { + #[allow(unused_mut)] fn from(code: HRESULT) -> Self { - let mut info = None; - unsafe { GetErrorInfo(0, &mut info as *mut _ as _) }; - Self { code, info } + #[cfg(windows)] + { + let mut info = None; + unsafe { GetErrorInfo(0, &mut info as *mut _ as _) }; + Self { code, info } + } + #[cfg(not(windows))] + { + Self { code } + } } } @@ -136,10 +211,88 @@ impl From for std::io::Error { #[cfg(feature = "std")] impl From for Error { fn from(from: std::io::Error) -> Self { - match from.raw_os_error() { - Some(status) => HRESULT::from_win32(status as u32).into(), - None => HRESULT(E_UNEXPECTED).into(), + #[cfg(windows)] + { + if let Some(status) = from.raw_os_error() { + return HRESULT::from_win32(status as u32).into(); + } } + + use std::io::ErrorKind; + + fn w32(code: u32) -> HRESULT { + HRESULT::from_win32(code).into() + } + + // Many ErrorKind variants are not yet stabilized. Some of them have + // obvious equivalents in Win32 error codes; some do not. We list as + // many as we can here so that enabling them (as they become stable) + // is easy. + + let hr: HRESULT = match from.kind() { + ErrorKind::NotFound => w32(ERROR_NOT_FOUND), + ErrorKind::PermissionDenied => w32(ERROR_ACCESS_DENIED), + ErrorKind::ConnectionRefused => w32(WSAECONNREFUSED), + ErrorKind::ConnectionReset => w32(WSAECONNRESET), + #[cfg(not_yet_stable)] + ErrorKind::HostUnreachable => w32(WSAEHOSTUNREACH), + #[cfg(not_yet_stable)] + ErrorKind::NetworkUnreachable => w32(WSAENETUNREACH), + ErrorKind::ConnectionAborted => w32(WSAECONNABORTED), + ErrorKind::NotConnected => w32(WSAENOTCONN), + ErrorKind::AddrInUse => w32(WSAEADDRINUSE), + ErrorKind::AddrNotAvailable => w32(WSAEADDRNOTAVAIL), + #[cfg(not_yet_stable)] + ErrorKind::NetworkDown => w32(WSAENETDOWN), + ErrorKind::BrokenPipe => w32(ERROR_BROKEN_PIPE), + ErrorKind::AlreadyExists => w32(ERROR_ALREADY_EXISTS), + ErrorKind::WouldBlock => w32(WSAEWOULDBLOCK), + #[cfg(not_yet_stable)] + ErrorKind::NotADirectory => _, + #[cfg(not_yet_stable)] + ErrorKind::IsADirectory => w32(ERROR_DIRECTORY_NOT_SUPPORTED), + #[cfg(not_yet_stable)] + ErrorKind::DirectoryNotEmpty => w32(ERROR_DIR_NOT_EMPTY), + #[cfg(not_yet_stable)] + ErrorKind::ReadOnlyFilesystem => w32(ERROR_FILE_READ_ONLY), + #[cfg(not_yet_stable)] + ErrorKind::FilesystemLoop => (), + #[cfg(not_yet_stable)] + ErrorKind::StaleNetworkFileHandle => (), + ErrorKind::InvalidInput => E_INVALIDARG, + ErrorKind::InvalidData => w32(ERROR_INVALID_DATA), + ErrorKind::TimedOut => w32(ERROR_TIMEOUT), + ErrorKind::WriteZero => E_FAIL, + #[cfg(not_yet_stable)] + ErrorKind::StorageFull => w32(ERROR_HANDLE_DISK_FULL), + #[cfg(not_yet_stable)] + ErrorKind::NotSeekable => w32(ERROR_SEEK_ON_DEVICE), + #[cfg(not_yet_stable)] + ErrorKind::FilesystemQuotaExceeded => w32(ERROR_DISK_QUOTA_EXCEEDED), + #[cfg(not_yet_stable)] + ErrorKind::FileTooLarge => _, + #[cfg(not_yet_stable)] + ErrorKind::ResourceBusy => _, + #[cfg(not_yet_stable)] + ErrorKind::ExecutableFileBusy => _, + #[cfg(not_yet_stable)] + ErrorKind::Deadlock => w32(ERROR_POSSIBLE_DEADLOCK), + #[cfg(not_yet_stable)] + ErrorKind::CrossesDevices => _, + #[cfg(not_yet_stable)] + ErrorKind::TooManyLinks => _, + #[cfg(not_yet_stable)] + ErrorKind::InvalidFilename => _, + #[cfg(not_yet_stable)] + ErrorKind::ArgumentListTooLong => _, + ErrorKind::Interrupted => w32(WSAEINTR), + ErrorKind::Unsupported => w32(ERROR_NOT_SUPPORTED), + ErrorKind::UnexpectedEof => w32(ERROR_HANDLE_EOF), + ErrorKind::OutOfMemory => E_OUTOFMEMORY, + ErrorKind::Other => E_FAIL, + _ => E_FAIL, + }; + Self::from_hresult(hr) } } @@ -147,6 +300,7 @@ impl From for Error { fn from(_: alloc::string::FromUtf16Error) -> Self { Self { code: HRESULT::from_win32(ERROR_NO_UNICODE_TRANSLATION), + #[cfg(windows)] info: None, } } @@ -156,6 +310,7 @@ impl From for Error { fn from(_: alloc::string::FromUtf8Error) -> Self { Self { code: HRESULT::from_win32(ERROR_NO_UNICODE_TRANSLATION), + #[cfg(windows)] info: None, } } @@ -165,6 +320,7 @@ impl From for Error { fn from(_: core::num::TryFromIntError) -> Self { Self { code: HRESULT::from_win32(ERROR_INVALID_DATA), + #[cfg(windows)] info: None, } } diff --git a/crates/libs/result/src/hresult.rs b/crates/libs/result/src/hresult.rs index fe69ee29f3b..1492750dd8f 100644 --- a/crates/libs/result/src/hresult.rs +++ b/crates/libs/result/src/hresult.rs @@ -2,7 +2,7 @@ use super::*; /// An error code value returned by most COM functions. #[repr(transparent)] -#[derive(Copy, Clone, Default, Eq, PartialEq)] +#[derive(Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] #[must_use] #[allow(non_camel_case_types)] pub struct HRESULT(pub i32); @@ -64,41 +64,52 @@ impl HRESULT { /// The error message describing the error. pub fn message(&self) -> String { - let mut message = HeapString::default(); - let mut code = self.0; - let mut module = 0; - - let mut flags = FORMAT_MESSAGE_ALLOCATE_BUFFER - | FORMAT_MESSAGE_FROM_SYSTEM - | FORMAT_MESSAGE_IGNORE_INSERTS; - - unsafe { - if self.0 & 0x1000_0000 == 0x1000_0000 { - code ^= 0x1000_0000; - flags |= FORMAT_MESSAGE_FROM_HMODULE; - - module = - LoadLibraryExA(b"ntdll.dll\0".as_ptr(), 0, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS); + #[cfg(windows)] + { + let mut message = HeapString::default(); + let mut code = self.0; + let mut module = 0; + + let mut flags = FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_IGNORE_INSERTS; + + unsafe { + if self.0 & 0x1000_0000 == 0x1000_0000 { + code ^= 0x1000_0000; + flags |= FORMAT_MESSAGE_FROM_HMODULE; + + module = LoadLibraryExA( + b"ntdll.dll\0".as_ptr(), + 0, + LOAD_LIBRARY_SEARCH_DEFAULT_DIRS, + ); + } + + let size = FormatMessageW( + flags, + module as _, + code as _, + 0, + &mut message.0 as *mut _ as *mut _, + 0, + core::ptr::null(), + ); + + if !message.0.is_null() && size > 0 { + String::from_utf16_lossy(wide_trim_end(core::slice::from_raw_parts( + message.0, + size as usize, + ))) + } else { + String::default() + } } + } - let size = FormatMessageW( - flags, - module as _, - code as _, - 0, - &mut message.0 as *mut _ as *mut _, - 0, - core::ptr::null(), - ); - - if !message.0.is_null() && size > 0 { - String::from_utf16_lossy(wide_trim_end(core::slice::from_raw_parts( - message.0, - size as usize, - ))) - } else { - String::default() - } + #[cfg(not(windows))] + { + return format!("0x{:08x}", self.0 as u32); } } @@ -119,6 +130,12 @@ impl HRESULT { error | 0x1000_0000 }) } + + /// Constructs an `HRESULT` from a `u32` value, by casting it. This is useful for defining + /// HRESULT constants. + pub const fn from_u32(error: u32) -> Self { + Self(error as i32) + } } impl From> for HRESULT { diff --git a/crates/libs/result/src/lib.rs b/crates/libs/result/src/lib.rs index 7e6601a2f3a..12eab610ab6 100644 --- a/crates/libs/result/src/lib.rs +++ b/crates/libs/result/src/lib.rs @@ -7,19 +7,26 @@ Learn more about Rust for Windows here: