From 6895347f4bb04de3490dc7e1f061a76c9c1629cd Mon Sep 17 00:00:00 2001 From: Kevin Mehall Date: Thu, 29 May 2025 21:22:54 -0600 Subject: [PATCH] Define a new Error type instead of re-exporting std::io::Error The default mapping from OS error codes to io::Error's messages is often wrong or misleading. We can do a lot better, but it takes a lot of error-handling code. --- src/descriptors.rs | 40 +---- src/device.rs | 70 +++------ src/error.rs | 169 +++++++++++++++++++++ src/lib.rs | 8 +- src/platform/linux_usbfs/device.rs | 146 ++++++++++++------ src/platform/linux_usbfs/enumeration.rs | 25 +-- src/platform/linux_usbfs/events.rs | 22 +-- src/platform/linux_usbfs/hotplug.rs | 13 +- src/platform/linux_usbfs/mod.rs | 26 ++++ src/platform/macos_iokit/device.rs | 155 ++++++++++++------- src/platform/macos_iokit/enumeration.rs | 26 +++- src/platform/macos_iokit/hotplug.rs | 8 +- src/platform/macos_iokit/iokit.rs | 13 +- src/platform/macos_iokit/iokit_usb.rs | 75 +++++++-- src/platform/macos_iokit/mod.rs | 17 +++ src/platform/windows_winusb/device.rs | 95 +++++++----- src/platform/windows_winusb/enumeration.rs | 25 +-- src/platform/windows_winusb/events.rs | 30 ++-- src/platform/windows_winusb/hotplug.rs | 10 +- src/platform/windows_winusb/hub.rs | 54 ++++--- src/platform/windows_winusb/mod.rs | 19 +++ src/platform/windows_winusb/registry.rs | 27 +++- src/platform/windows_winusb/util.rs | 7 +- src/transfer/mod.rs | 10 +- 24 files changed, 737 insertions(+), 353 deletions(-) create mode 100644 src/error.rs diff --git a/src/descriptors.rs b/src/descriptors.rs index e957a51..c88d65a 100644 --- a/src/descriptors.rs +++ b/src/descriptors.rs @@ -2,17 +2,11 @@ //! //! Descriptors are blocks of data that describe the functionality of a USB device. -use std::{ - collections::BTreeMap, - fmt::{Debug, Display}, - iter, - num::NonZeroU8, - ops::Deref, -}; +use std::{collections::BTreeMap, fmt::Debug, iter, num::NonZeroU8, ops::Deref}; use log::warn; -use crate::{transfer::Direction, Error}; +use crate::transfer::Direction; pub(crate) const DESCRIPTOR_TYPE_DEVICE: u8 = 0x01; pub(crate) const DESCRIPTOR_LEN_DEVICE: u8 = 18; @@ -710,34 +704,6 @@ pub enum TransferType { Interrupt = 3, } -/// Error from [`crate::Device::active_configuration`] -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct ActiveConfigurationError { - pub(crate) configuration_value: u8, -} - -impl Display for ActiveConfigurationError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if self.configuration_value == 0 { - write!(f, "device is not configured") - } else { - write!( - f, - "no descriptor found for active configuration {}", - self.configuration_value - ) - } - } -} - -impl std::error::Error for ActiveConfigurationError {} - -impl From for Error { - fn from(value: ActiveConfigurationError) -> Self { - Error::other(value) - } -} - /// Split a chain of concatenated configuration descriptors by `wTotalLength` #[allow(unused)] pub(crate) fn parse_concatenated_config_descriptors( @@ -914,7 +880,7 @@ fn test_linux_root_hub() { fn test_dell_webcam() { let c = ConfigurationDescriptor(&[ 0x09, 0x02, 0xa3, 0x02, 0x02, 0x01, 0x00, 0x80, 0xfa, - + // unknown (skipped) 0x28, 0xff, 0x42, 0x49, 0x53, 0x54, 0x00, 0x01, 0x06, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd1, 0x10, 0xd0, 0x07, 0xd2, 0x11, 0xf4, 0x01, diff --git a/src/device.rs b/src/device.rs index 16e906a..15b4d26 100644 --- a/src/device.rs +++ b/src/device.rs @@ -1,19 +1,18 @@ use crate::{ descriptors::{ - decode_string_descriptor, validate_string_descriptor, ActiveConfigurationError, - ConfigurationDescriptor, DeviceDescriptor, InterfaceDescriptor, DESCRIPTOR_TYPE_STRING, + decode_string_descriptor, validate_string_descriptor, ConfigurationDescriptor, + DeviceDescriptor, InterfaceDescriptor, DESCRIPTOR_TYPE_STRING, }, platform, transfer::{ Buffer, BulkOrInterrupt, Completion, ControlIn, ControlOut, Direction, EndpointDirection, EndpointType, TransferError, }, - DeviceInfo, Error, MaybeFuture, Speed, + ActiveConfigurationError, DeviceInfo, Error, ErrorKind, GetDescriptorError, MaybeFuture, Speed, }; use log::{error, warn}; use std::{ future::{poll_fn, Future}, - io::ErrorKind, marker::PhantomData, num::NonZeroU8, sync::Arc, @@ -51,9 +50,7 @@ impl Device { Device { backend } } - pub(crate) fn open( - d: &DeviceInfo, - ) -> impl MaybeFuture> { + pub(crate) fn open(d: &DeviceInfo) -> impl MaybeFuture> { platform::Device::from_device_info(d).map(|d| d.map(Device::wrap)) } @@ -181,13 +178,14 @@ impl Device { desc_index: u8, language_id: u16, timeout: Duration, - ) -> impl MaybeFuture, Error>> { + ) -> impl MaybeFuture, GetDescriptorError>> { #[cfg(target_os = "windows")] { let _ = timeout; self.backend .clone() .get_descriptor(desc_type, desc_index, language_id) + .map(|r| r.map_err(GetDescriptorError::Transfer)) } #[cfg(not(target_os = "windows"))] @@ -206,7 +204,7 @@ impl Device { }, timeout, ) - .map(|r| Ok(r?)) + .map(|r| r.map_err(GetDescriptorError::Transfer)) } } @@ -218,16 +216,13 @@ impl Device { pub fn get_string_descriptor_supported_languages( &self, timeout: Duration, - ) -> impl MaybeFuture, Error>> { + ) -> impl MaybeFuture, GetDescriptorError>> { self.get_descriptor(DESCRIPTOR_TYPE_STRING, 0, 0, timeout) .map(move |r| { let data = r?; if !validate_string_descriptor(&data) { error!("String descriptor language list read {data:?}, not a valid string descriptor"); - return Err(Error::new( - ErrorKind::InvalidData, - "string descriptor data was invalid", - )); + return Err(GetDescriptorError::InvalidDescriptor) } //TODO: Use array_chunks once stable @@ -252,7 +247,7 @@ impl Device { desc_index: NonZeroU8, language_id: u16, timeout: Duration, - ) -> impl MaybeFuture> { + ) -> impl MaybeFuture> { self.get_descriptor( DESCRIPTOR_TYPE_STRING, desc_index.get(), @@ -261,9 +256,7 @@ impl Device { ) .map(|r| { let data = r?; - decode_string_descriptor(&data).map_err(|_| { - Error::new(ErrorKind::InvalidData, "string descriptor data was invalid") - }) + decode_string_descriptor(&data).map_err(|_| GetDescriptorError::InvalidDescriptor) }) } @@ -500,16 +493,23 @@ impl Interface { pub fn endpoint( &self, address: u8, - ) -> Result, ClaimEndpointError> { + ) -> Result, Error> { let intf_desc = self.descriptor(); let ep_desc = intf_desc.and_then(|desc| desc.endpoints().find(|ep| ep.address() == address)); let Some(ep_desc) = ep_desc else { - return Err(ClaimEndpointError::InvalidAddress); + return Err(Error::new( + ErrorKind::NotFound, + "specified endpoint does not exist on this interface", + )); }; - if ep_desc.transfer_type() != EpType::TYPE || address & Direction::MASK != Dir::DIR as u8 { - return Err(ClaimEndpointError::InvalidType); + if address & Direction::MASK != Dir::DIR as u8 { + return Err(Error::new(ErrorKind::Other, "incorrect endpoint direction")); + } + + if ep_desc.transfer_type() != EpType::TYPE { + return Err(Error::new(ErrorKind::Other, "incorrect endpoint type")); } let backend = self.backend.endpoint(ep_desc)?; @@ -521,32 +521,6 @@ impl Interface { } } -/// Error from [`Interface::endpoint`]. -#[non_exhaustive] -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum ClaimEndpointError { - /// The specified address does not exist on this interface and alternate setting - InvalidAddress, - - /// The type or direction does not match the endpoint descriptor for this address - InvalidType, - - /// The endpoint is already claimed - Busy, -} - -impl std::fmt::Display for ClaimEndpointError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ClaimEndpointError::InvalidAddress => write!(f, "invalid endpoint address"), - ClaimEndpointError::InvalidType => write!(f, "incorrect endpoint type or direction"), - ClaimEndpointError::Busy => write!(f, "endpoint is already claimed"), - } - } -} - -impl std::error::Error for ClaimEndpointError {} - /// Exclusive access to an endpoint of a USB device. /// /// Obtain an `Endpoint` with the [`Interface::endpoint`] method. diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..062586b --- /dev/null +++ b/src/error.rs @@ -0,0 +1,169 @@ +use std::{fmt::Display, io, num::NonZeroU32}; + +use crate::{platform::format_os_error_code, transfer::TransferError}; + +/// Error returned from `nusb` operations other than transfers. +#[derive(Debug, Clone)] +pub struct Error { + pub(crate) kind: ErrorKind, + pub(crate) code: Option, + pub(crate) message: &'static str, +} + +impl Error { + pub(crate) fn new(kind: ErrorKind, message: &'static str) -> Self { + Self { + kind, + code: None, + message, + } + } + + #[track_caller] + pub(crate) fn log_error(self) -> Self { + log::error!("{}", self); + self + } + + #[track_caller] + pub(crate) fn log_debug(self) -> Self { + log::debug!("{}", self); + self + } + + /// Get the error kind. + pub fn kind(&self) -> ErrorKind { + self.kind + } + + /// Get the error code from the OS, if applicable. + /// + /// * On Linux this is the `errno` value. + /// * On Windows this is the `WIN32_ERROR` value. + /// * On macOS this is the `IOReturn` value. + pub fn os_error(&self) -> Option { + self.code.map(|c| c.get()) + } +} + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.message)?; + if let Some(code) = self.code { + write!(f, " (")?; + format_os_error_code(f, code.get())?; + write!(f, ")")?; + } + Ok(()) + } +} + +impl std::error::Error for Error {} + +impl From for io::Error { + fn from(err: Error) -> Self { + let kind = match err.kind { + ErrorKind::Disconnected => io::ErrorKind::NotConnected, + ErrorKind::Busy => io::ErrorKind::Other, // TODO: ResourceBusy + ErrorKind::PermissionDenied => io::ErrorKind::PermissionDenied, + ErrorKind::NotFound => io::ErrorKind::NotFound, + ErrorKind::Unsupported => io::ErrorKind::Unsupported, + ErrorKind::Other => io::ErrorKind::Other, + }; + io::Error::new(kind, err) + } +} + +/// General category of error as part of an [`Error`]. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum ErrorKind { + /// Device is disconnected. + Disconnected, + + /// Device, interface, or endpoint is in use by another application, kernel driver, or handle. + Busy, + + /// This user or application does not have permission to perform the requested operation. + PermissionDenied, + + /// Requested configuration, interface, or alternate setting not found + NotFound, + + /// The requested operation is not supported by the platform or its currently-configured driver. + Unsupported, + + /// Uncategorized error. + Other, +} + +/// Error from [`crate::Device::active_configuration`] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct ActiveConfigurationError { + pub(crate) configuration_value: u8, +} + +impl Display for ActiveConfigurationError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.configuration_value == 0 { + write!(f, "device is not configured") + } else { + write!( + f, + "no descriptor found for active configuration {}", + self.configuration_value + ) + } + } +} + +impl std::error::Error for ActiveConfigurationError {} + +impl From for Error { + fn from(value: ActiveConfigurationError) -> Self { + let message = if value.configuration_value == 0 { + "device is not configured" + } else { + "no descriptor found for active configuration" + }; + Error::new(ErrorKind::Other, message) + } +} + +impl From for std::io::Error { + fn from(value: ActiveConfigurationError) -> Self { + std::io::Error::other(value) + } +} + +/// Error for descriptor reads. +#[derive(Debug, Copy, Clone)] +pub enum GetDescriptorError { + /// Transfer error when getting the descriptor. + Transfer(TransferError), + + /// Invalid descriptor data + InvalidDescriptor, +} + +impl Display for GetDescriptorError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + GetDescriptorError::Transfer(e) => write!(f, "{}", e), + GetDescriptorError::InvalidDescriptor => write!(f, "invalid descriptor"), + } + } +} + +impl std::error::Error for GetDescriptorError {} + +impl From for std::io::Error { + fn from(value: GetDescriptorError) -> Self { + match value { + GetDescriptorError::Transfer(e) => e.into(), + GetDescriptorError::InvalidDescriptor => { + std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid descriptor") + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index c342ad8..b39c965 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -134,8 +134,6 @@ //! These features do not affect and are not required for transfers, which are //! implemented on top of natively-async OS APIs. -use std::io; - mod platform; pub mod descriptors; @@ -143,7 +141,7 @@ mod enumeration; pub use enumeration::{BusInfo, DeviceId, DeviceInfo, InterfaceInfo, Speed, UsbControllerType}; mod device; -pub use device::{ClaimEndpointError, Device, Endpoint, Interface}; +pub use device::{Device, Endpoint, Interface}; pub mod transfer; @@ -154,8 +152,8 @@ pub use maybe_future::MaybeFuture; mod bitset; -/// OS error returned from operations other than transfers. -pub type Error = io::Error; +mod error; +pub use error::{ActiveConfigurationError, Error, ErrorKind, GetDescriptorError}; /// Get an iterator listing the connected devices. /// diff --git a/src/platform/linux_usbfs/device.rs b/src/platform/linux_usbfs/device.rs index e68e5a7..1d67fb6 100644 --- a/src/platform/linux_usbfs/device.rs +++ b/src/platform/linux_usbfs/device.rs @@ -2,7 +2,7 @@ use std::{ collections::{BTreeMap, VecDeque}, ffi::c_void, fs::File, - io::{ErrorKind, Read, Seek}, + io::{Read, Seek}, mem::ManuallyDrop, path::PathBuf, sync::{ @@ -34,7 +34,6 @@ use crate::{ parse_concatenated_config_descriptors, ConfigurationDescriptor, DeviceDescriptor, EndpointDescriptor, TransferType, DESCRIPTOR_LEN_DEVICE, }, - device::ClaimEndpointError, maybe_future::{blocking::Blocking, MaybeFuture}, transfer::{ internal::{ @@ -43,7 +42,7 @@ use crate::{ request_type, Buffer, Completion, ControlIn, ControlOut, ControlType, Direction, Recipient, TransferError, }, - DeviceInfo, Error, Speed, + DeviceInfo, Error, ErrorKind, Speed, }; #[derive(PartialEq, Eq, PartialOrd, Ord)] @@ -82,7 +81,19 @@ impl LinuxDevice { Blocking::new(move || { let path = PathBuf::from(format!("/dev/bus/usb/{busnum:03}/{devnum:03}")); let fd = rustix::fs::open(&path, OFlags::RDWR | OFlags::CLOEXEC, Mode::empty()) - .inspect_err(|e| warn!("Failed to open device {path:?}: {e}"))?; + .map_err(|e| { + match e { + Errno::NOENT => { + Error::new_os(ErrorKind::Disconnected, "device not found", e) + } + Errno::PERM => { + Error::new_os(ErrorKind::PermissionDenied, "permission denied", e) + } + e => Error::new_os(ErrorKind::Other, "failed to open device", e), + } + .log_debug() + })?; + Self::create_inner(fd, Some(sysfs_path)) }) } @@ -100,18 +111,18 @@ impl LinuxDevice { fd: OwnedFd, sysfs: Option, ) -> Result, Error> { - let descriptors = read_all_from_fd(&fd)?; + let descriptors = read_all_from_fd(&fd).map_err(|e| { + Error::new_io(ErrorKind::Other, "failed to read descriptors", e).log_error() + })?; let Some(_) = DeviceDescriptor::new(&descriptors) else { - return Err(Error::new( - ErrorKind::InvalidData, - "invalid device descriptor", - )); + return Err(Error::new(ErrorKind::Other, "invalid device descriptor")); }; let active_config: u8 = if let Some(sysfs) = sysfs.as_ref() { - sysfs.read_attr("bConfigurationValue").inspect_err(|e| { + sysfs.read_attr("bConfigurationValue").map_err(|e| { warn!("failed to read sysfs bConfigurationValue: {e}"); + Error::new(ErrorKind::Other, "failed to read sysfs bConfigurationValue") })? } else { request_configuration(&fd).unwrap_or_else(|()| { @@ -132,7 +143,7 @@ impl LinuxDevice { rustix::time::TimerfdClockId::Monotonic, TimerfdFlags::CLOEXEC | TimerfdFlags::NONBLOCK, ) - .inspect_err(|e| log::error!("Failed to create timerfd: {e}"))?; + .map_err(|e| Error::new_os(ErrorKind::Other, "failed to create timerfd", e).log_error())?; let arc = Arc::new_cyclic(|weak| { let events_id = DEVICES.lock().unwrap().insert(weak.clone()); @@ -313,7 +324,12 @@ impl LinuxDevice { configuration: u8, ) -> impl MaybeFuture> { Blocking::new(move || { - usbfs::set_configuration(&self.fd, configuration)?; + usbfs::set_configuration(&self.fd, configuration).map_err(|e| match e { + Errno::INVAL => Error::new_os(ErrorKind::NotFound, "configuration not found", e), + Errno::BUSY => Error::new_os(ErrorKind::Busy, "device is busy", e), + Errno::NODEV => Error::new_os(ErrorKind::Disconnected, "device disconnected", e), + _ => Error::new_os(ErrorKind::Other, "failed to set configuration", e), + })?; self.active_config.store(configuration, Ordering::SeqCst); Ok(()) }) @@ -321,8 +337,11 @@ impl LinuxDevice { pub(crate) fn reset(self: Arc) -> impl MaybeFuture> { Blocking::new(move || { - usbfs::reset(&self.fd)?; - Ok(()) + usbfs::reset(&self.fd).map_err(|e| match e { + Errno::BUSY => Error::new_os(ErrorKind::Busy, "device is busy", e), + Errno::NODEV => Error::new_os(ErrorKind::Disconnected, "device disconnected", e), + _ => Error::new_os(ErrorKind::Other, "failed to reset device", e), + }) }) } @@ -352,27 +371,40 @@ impl LinuxDevice { }) } + fn handle_claim_interface_result( + self: Arc, + interface_number: u8, + result: Result<(), Errno>, + reattach: bool, + ) -> Result, Error> { + result.map_err(|e| { + match e { + Errno::INVAL => Error::new_os(ErrorKind::NotFound, "interface not found", e), + Errno::BUSY => Error::new_os(ErrorKind::Busy, "interface is busy", e), + Errno::NODEV => Error::new_os(ErrorKind::Disconnected, "device disconnected", e), + _ => Error::new_os(ErrorKind::Other, "failed to claim interface", e), + } + .log_error() + })?; + debug!( + "Claimed interface {interface_number} on device id {dev}", + dev = self.events_id + ); + Ok(Arc::new(LinuxInterface { + device: self, + interface_number, + reattach, + state: Mutex::new(Default::default()), + })) + } + pub(crate) fn claim_interface( self: Arc, interface_number: u8, ) -> impl MaybeFuture, Error>> { Blocking::new(move || { - usbfs::claim_interface(&self.fd, interface_number).inspect_err(|e| { - warn!( - "Failed to claim interface {interface_number} on device id {dev}: {e}", - dev = self.events_id - ) - })?; - debug!( - "Claimed interface {interface_number} on device id {dev}", - dev = self.events_id - ); - Ok(Arc::new(LinuxInterface { - device: self, - interface_number, - reattach: false, - state: Mutex::new(Default::default()), - })) + let result = usbfs::claim_interface(&self.fd, interface_number); + self.handle_claim_interface_result(interface_number, result, false) }) } @@ -381,17 +413,8 @@ impl LinuxDevice { interface_number: u8, ) -> impl MaybeFuture, Error>> { Blocking::new(move || { - usbfs::detach_and_claim_interface(&self.fd, interface_number)?; - debug!( - "Detached and claimed interface {interface_number} on device id {dev}", - dev = self.events_id - ); - Ok(Arc::new(LinuxInterface { - device: self, - interface_number, - reattach: true, - state: Mutex::new(Default::default()), - })) + let result = usbfs::detach_and_claim_interface(&self.fd, interface_number); + self.handle_claim_interface_result(interface_number, result, true) }) } @@ -400,7 +423,12 @@ impl LinuxDevice { self: &Arc, interface_number: u8, ) -> Result<(), Error> { - usbfs::detach_kernel_driver(&self.fd, interface_number).map_err(|e| e.into()) + usbfs::detach_kernel_driver(&self.fd, interface_number).map_err(|e| match e { + Errno::INVAL => Error::new_os(ErrorKind::NotFound, "interface not found", e), + Errno::NODEV => Error::new_os(ErrorKind::Disconnected, "device disconnected", e), + Errno::NODATA => Error::new_os(ErrorKind::Other, "no kernel driver attached", e), + _ => Error::new_os(ErrorKind::Other, "failed to detach kernel driver", e), + }) } #[cfg(target_os = "linux")] @@ -408,7 +436,12 @@ impl LinuxDevice { self: &Arc, interface_number: u8, ) -> Result<(), Error> { - usbfs::attach_kernel_driver(&self.fd, interface_number).map_err(|e| e.into()) + usbfs::attach_kernel_driver(&self.fd, interface_number).map_err(|e| match e { + Errno::INVAL => Error::new_os(ErrorKind::NotFound, "interface not found", e), + Errno::NODEV => Error::new_os(ErrorKind::Disconnected, "device disconnected", e), + Errno::BUSY => Error::new_os(ErrorKind::Busy, "kernel driver already attached", e), + _ => Error::new_os(ErrorKind::Other, "failed to attach kernel driver", e), + }) } pub(crate) fn submit(&self, transfer: Idle) -> Pending { @@ -583,16 +616,26 @@ impl LinuxInterface { Blocking::new(move || { let mut state = self.state.lock().unwrap(); if !state.endpoints.is_empty() { - // TODO: Use ErrorKind::ResourceBusy once compatible with MSRV - return Err(Error::other( - "must drop endpoints before changing alt setting", + return Err(Error::new( + ErrorKind::Busy, + "can't change alternate setting while endpoints are in use", )); } + usbfs::set_interface(&self.device.fd, self.interface_number, alt_setting).map_err( + |e| match e { + Errno::INVAL => { + Error::new_os(ErrorKind::NotFound, "alternate setting not found", e) + } + Errno::NODEV => { + Error::new_os(ErrorKind::Disconnected, "device disconnected", e) + } + _ => Error::new_os(ErrorKind::Other, "failed to set alternate setting", e), + }, + )?; debug!( "Set interface {} alt setting to {alt_setting}", self.interface_number ); - usbfs::set_interface(&self.device.fd, self.interface_number, alt_setting)?; state.alt_setting = alt_setting; Ok(()) }) @@ -601,7 +644,7 @@ impl LinuxInterface { pub fn endpoint( self: &Arc, descriptor: EndpointDescriptor, - ) -> Result { + ) -> Result { let address = descriptor.address(); let ep_type = descriptor.transfer_type(); let max_packet_size = descriptor.max_packet_size(); @@ -609,7 +652,7 @@ impl LinuxInterface { let mut state = self.state.lock().unwrap(); if state.endpoints.is_set(address) { - return Err(ClaimEndpointError::Busy); + return Err(Error::new(ErrorKind::Busy, "endpoint already in use")); } state.endpoints.set(address); @@ -730,7 +773,10 @@ impl LinuxEndpoint { Blocking::new(move || { let endpoint = inner.address; debug!("Clear halt, endpoint {endpoint:02x}"); - Ok(usbfs::clear_halt(&inner.interface.device.fd, endpoint)?) + usbfs::clear_halt(&inner.interface.device.fd, endpoint).map_err(|e| match e { + Errno::NODEV => Error::new_os(ErrorKind::Disconnected, "device disconnected", e), + _ => Error::new_os(ErrorKind::Other, "failed to clear halt", e), + }) }) } diff --git a/src/platform/linux_usbfs/enumeration.rs b/src/platform/linux_usbfs/enumeration.rs index 62250e7..8bb6c10 100644 --- a/src/platform/linux_usbfs/enumeration.rs +++ b/src/platform/linux_usbfs/enumeration.rs @@ -8,6 +8,7 @@ use std::str::FromStr; use crate::enumeration::InterfaceInfo; use crate::maybe_future::{MaybeFuture, Ready}; +use crate::ErrorKind; use crate::{BusInfo, DeviceInfo, Error, Speed, UsbControllerType}; #[derive(Debug, Clone)] @@ -41,12 +42,6 @@ impl std::error::Error for SysfsError { } } -impl From for io::Error { - fn from(value: SysfsError) -> Self { - io::Error::other(Box::new(value)) - } -} - impl SysfsPath { fn parse_attr( &self, @@ -116,12 +111,24 @@ impl FromHexStr for u16 { } } -const SYSFS_USB_PREFIX: &str = "/sys/bus/usb/devices/"; +fn sysfs_list_usb() -> Result { + fs::read_dir("/sys/bus/usb/devices/").map_err(|e| match e.kind() { + io::ErrorKind::NotFound => { + Error::new_io(ErrorKind::Other, "/sys/bus/usb/devices/ not found", e) + } + io::ErrorKind::PermissionDenied => Error::new_io( + ErrorKind::PermissionDenied, + "/sys/bus/usb/devices/ permission denied", + e, + ), + _ => Error::new_io(ErrorKind::Other, "failed to open /sys/bus/usb/devices/", e), + }) +} pub fn list_devices() -> impl MaybeFuture, Error>> { Ready((|| { - Ok(fs::read_dir(SYSFS_USB_PREFIX)?.flat_map(|entry| { + Ok(sysfs_list_usb()?.flat_map(|entry| { let path = entry.ok()?.path(); let name = path.file_name()?; @@ -147,7 +154,7 @@ pub fn list_devices() -> impl MaybeFuture Result, Error> { - Ok(fs::read_dir(SYSFS_USB_PREFIX)?.filter_map(|entry| { + Ok(sysfs_list_usb()?.filter_map(|entry| { let path = entry.ok()?.path(); let name = path.file_name()?; diff --git a/src/platform/linux_usbfs/events.rs b/src/platform/linux_usbfs/events.rs index 93eade0..ad1cd93 100644 --- a/src/platform/linux_usbfs/events.rs +++ b/src/platform/linux_usbfs/events.rs @@ -11,7 +11,7 @@ //! on a device use the same file descriptor, putting USB-specific //! dispatch in the event loop avoids additonal synchronization. -use crate::Error; +use crate::{Error, ErrorKind}; use once_cell::sync::OnceCell; use rustix::{ event::epoll::{self, EventData, EventFlags}, @@ -19,7 +19,7 @@ use rustix::{ io::Errno, }; use slab::Slab; -use std::{io, mem::MaybeUninit, sync::Mutex, task::Waker, thread}; +use std::{mem::MaybeUninit, sync::Mutex, task::Waker, thread}; use super::Device; @@ -61,8 +61,8 @@ pub(super) fn register_fd(fd: BorrowedFd, tag: Tag, flags: EventFlags) -> Result let mut start_thread = false; let epoll_fd = EPOLL_FD.get_or_try_init(|| { start_thread = true; - epoll::create(epoll::CreateFlags::CLOEXEC).inspect_err(|e| { - log::error!("Failed to initialize epoll: {e}"); + epoll::create(epoll::CreateFlags::CLOEXEC).map_err(|e| { + Error::new_os(ErrorKind::Other, "failed to initialize epoll", e).log_error() }) })?; @@ -70,9 +70,8 @@ pub(super) fn register_fd(fd: BorrowedFd, tag: Tag, flags: EventFlags) -> Result thread::spawn(event_loop); } - epoll::add(epoll_fd, fd, tag.as_event_data(), flags).inspect_err(|e| { - log::error!("Failed to add epoll watch: {e}"); - })?; + epoll::add(epoll_fd, fd, tag.as_event_data(), flags) + .map_err(|e| Error::new_os(ErrorKind::Other, "failed to add epoll watch", e).log_error())?; Ok(()) } @@ -115,13 +114,13 @@ pub(crate) struct Async { } impl Async { - pub fn new(inner: T) -> Result { + pub fn new(inner: T) -> Result { let id = WAKERS.lock().unwrap().insert(None); register_fd(inner.as_fd(), Tag::Waker(id), EventFlags::empty())?; Ok(Async { inner, id }) } - pub fn register(&self, waker: &Waker) -> Result<(), io::Error> { + pub fn register(&self, waker: &Waker) -> Result<(), Error> { WAKERS .lock() .unwrap() @@ -134,7 +133,10 @@ impl Async { self.inner.as_fd(), Tag::Waker(self.id).as_event_data(), EventFlags::ONESHOT | EventFlags::IN, - )?; + ) + .map_err(|e| { + Error::new_os(ErrorKind::Other, "failed to modify epoll watch", e).log_error() + })?; Ok(()) } } diff --git a/src/platform/linux_usbfs/hotplug.rs b/src/platform/linux_usbfs/hotplug.rs index 28ddc04..a8f8298 100644 --- a/src/platform/linux_usbfs/hotplug.rs +++ b/src/platform/linux_usbfs/hotplug.rs @@ -10,7 +10,7 @@ use rustix::{ }; use std::{mem::MaybeUninit, os::unix::prelude::BorrowedFd, path::Path, task::Poll}; -use crate::{hotplug::HotplugEvent, Error}; +use crate::{hotplug::HotplugEvent, Error, ErrorKind}; use super::{enumeration::probe_device, events::Async, SysfsPath}; @@ -28,8 +28,15 @@ impl LinuxHotplugWatch { SocketType::RAW, SocketFlags::CLOEXEC, Some(netlink::KOBJECT_UEVENT), - )?; - bind(&fd, &SocketAddrNetlink::new(0, UDEV_MULTICAST_GROUP))?; + ) + .map_err(|e| { + Error::new_os(ErrorKind::Other, "failed to open udev netlink socket", e).log_error() + })?; + + bind(&fd, &SocketAddrNetlink::new(0, UDEV_MULTICAST_GROUP)).map_err(|e| { + Error::new_os(ErrorKind::Other, "failed to bind udev netlink socket", e).log_error() + })?; + Ok(LinuxHotplugWatch { fd: Async::new(fd)?, }) diff --git a/src/platform/linux_usbfs/mod.rs b/src/platform/linux_usbfs/mod.rs index 917edf6..20be16f 100644 --- a/src/platform/linux_usbfs/mod.rs +++ b/src/platform/linux_usbfs/mod.rs @@ -1,4 +1,7 @@ mod transfer; +use std::io; +use std::num::NonZeroU32; + use rustix::io::Errno; pub(crate) use transfer::TransferData; mod usbfs; @@ -16,6 +19,7 @@ mod hotplug; pub(crate) use hotplug::LinuxHotplugWatch as HotplugWatch; use crate::transfer::TransferError; +use crate::ErrorKind; #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] pub struct DeviceId { @@ -35,3 +39,25 @@ fn errno_to_transfer_error(e: Errno) -> TransferError { _ => TransferError::Unknown(e.raw_os_error() as u32), } } + +pub fn format_os_error_code(f: &mut std::fmt::Formatter<'_>, code: u32) -> std::fmt::Result { + write!(f, "errno {}", code) +} + +impl crate::error::Error { + pub(crate) fn new_os(kind: ErrorKind, message: &'static str, code: Errno) -> Self { + Self { + kind, + code: NonZeroU32::new(code.raw_os_error() as u32), + message, + } + } + + pub(crate) fn new_io(kind: ErrorKind, message: &'static str, err: io::Error) -> Self { + Self { + kind, + code: err.raw_os_error().and_then(|i| NonZeroU32::new(i as u32)), + message, + } + } +} diff --git a/src/platform/macos_iokit/device.rs b/src/platform/macos_iokit/device.rs index 9030f7d..04d6c25 100644 --- a/src/platform/macos_iokit/device.rs +++ b/src/platform/macos_iokit/device.rs @@ -1,7 +1,6 @@ use std::{ collections::VecDeque, ffi::c_void, - io::ErrorKind, mem::ManuallyDrop, sync::{ atomic::{AtomicU8, AtomicUsize, Ordering}, @@ -17,7 +16,6 @@ use log::{debug, error}; use crate::{ bitset::EndpointBitSet, descriptors::{ConfigurationDescriptor, DeviceDescriptor, EndpointDescriptor}, - device::ClaimEndpointError, maybe_future::blocking::Blocking, transfer::{ internal::{ @@ -25,13 +23,13 @@ use crate::{ }, Buffer, Completion, ControlIn, ControlOut, Direction, TransferError, }, - DeviceInfo, Error, MaybeFuture, Speed, + DeviceInfo, Error, ErrorKind, MaybeFuture, Speed, }; use super::{ enumeration::{device_descriptor_from_fields, get_integer_property, service_by_registry_id}, events::{add_event_source, EventRegistration}, - iokit::{call_iokit_function, check_iokit_return}, + iokit::call_iokit_function, iokit_c::IOUSBDevRequestTO, iokit_usb::{IoKitDevice, IoKitInterface}, TransferData, @@ -70,23 +68,32 @@ impl MacDevice { log::info!("Opening device from registry id {}", registry_id); let service = service_by_registry_id(registry_id)?; let device = IoKitDevice::new(&service)?; - let _event_registration = add_event_source(device.create_async_event_source()?); + let event_source = device.create_async_event_source().map_err(|e| { + Error::new_os(ErrorKind::Other, "failed to create async event source", e) + .log_error() + })?; + let _event_registration = add_event_source(event_source); - let opened = match unsafe { call_iokit_function!(device.raw, USBDeviceOpen()) } { - io_kit_sys::ret::kIOReturnSuccess => true, - err => { - // Most methods don't require USBDeviceOpen() so this can be ignored - // to allow different processes to open different interfaces. - log::debug!("Could not open device for exclusive access: {err:x}"); - false - } - }; + let opened = device + .open() + .inspect_err(|err| { + log::debug!("Could not open device for exclusive access: 0x{err:08x}"); + }) + .is_ok(); - let device_descriptor = device_descriptor_from_fields(&service) - .ok_or_else(|| Error::other("could not read properties for device descriptor"))?; + let device_descriptor = device_descriptor_from_fields(&service).ok_or_else(|| { + Error::new( + ErrorKind::Other, + "could not read properties for device descriptor", + ) + })?; - let num_configs = device.get_number_of_configurations().inspect_err(|e| { - log::warn!("failed to get number of configurations: {e}"); + let num_configs = device.get_number_of_configurations().map_err(|e| { + Error::new_os( + ErrorKind::Other, + "failed to get number of configurations", + e, + ) })?; let config_descriptors: Vec> = (0..num_configs) @@ -149,12 +156,22 @@ impl MacDevice { fn require_open_exclusive(&self) -> Result<(), Error> { let mut is_open_exclusive = self.is_open_exclusive.lock().unwrap(); if !*is_open_exclusive { - unsafe { check_iokit_return(call_iokit_function!(self.device.raw, USBDeviceOpen()))? }; + self.device.open().map_err(|e| match e { + io_kit_sys::ret::kIOReturnNoDevice => { + Error::new_os(ErrorKind::Disconnected, "device disconnected", e) + } + _ => Error::new_os( + ErrorKind::Other, + "could not open device for exclusive access", + e, + ), + })?; *is_open_exclusive = true; } if self.claimed_interfaces.load(Ordering::Relaxed) != 0 { - return Err(Error::other( + return Err(Error::new( + ErrorKind::Busy, "cannot perform this operation while interfaces are claimed", )); } @@ -168,12 +185,17 @@ impl MacDevice { ) -> impl MaybeFuture> { Blocking::new(move || { self.require_open_exclusive()?; - unsafe { - check_iokit_return(call_iokit_function!( - self.device.raw, - SetConfiguration(configuration) - ))? - } + self.device + .set_configuration(configuration) + .map_err(|e| match e { + io_kit_sys::ret::kIOReturnNoDevice => { + Error::new_os(ErrorKind::Disconnected, "device disconnected", e) + } + io_kit_sys::ret::kIOReturnNotFound => { + Error::new_os(ErrorKind::NotFound, "configuration not found", e) + } + _ => Error::new_os(ErrorKind::Other, "failed to set configuration", e), + })?; log::debug!("Set configuration {configuration}"); self.active_config.store(configuration, Ordering::SeqCst); Ok(()) @@ -183,12 +205,12 @@ impl MacDevice { pub(crate) fn reset(self: Arc) -> impl MaybeFuture> { Blocking::new(move || { self.require_open_exclusive()?; - unsafe { - check_iokit_return(call_iokit_function!( - self.device.raw, - USBDeviceReEnumerate(0) - )) - } + self.device.reset().map_err(|e| match e { + io_kit_sys::ret::kIOReturnNoDevice => { + Error::new_os(ErrorKind::Disconnected, "device disconnected", e) + } + _ => Error::new_os(ErrorKind::Other, "failed to reset device", e), + }) }) } @@ -199,7 +221,10 @@ impl MacDevice { Blocking::new(move || { let intf_service = self .device - .create_interface_iterator()? + .create_interface_iterator() + .map_err(|e| { + Error::new_os(ErrorKind::Other, "failed to create interface iterator", e) + })? .find(|io_service| { get_integer_property(io_service, "bInterfaceNumber") == Some(interface_number as i64) @@ -207,9 +232,23 @@ impl MacDevice { .ok_or(Error::new(ErrorKind::NotFound, "interface not found"))?; let mut interface = IoKitInterface::new(intf_service)?; - let _event_registration = add_event_source(interface.create_async_event_source()?); + let source = interface.create_async_event_source().map_err(|e| { + Error::new_os(ErrorKind::Other, "failed to create async event source", e) + .log_error() + })?; + let _event_registration = add_event_source(source); - interface.open()?; + interface.open().map_err(|e| match e { + io_kit_sys::ret::kIOReturnExclusiveAccess => Error::new_os( + ErrorKind::Busy, + "could not open interface for exclusive access", + e, + ), + io_kit_sys::ret::kIOReturnNoDevice => { + Error::new_os(ErrorKind::Disconnected, "device disconnected", e) + } + _ => Error::new_os(ErrorKind::Other, "failed to open interface", e), + })?; self.claimed_interfaces.fetch_add(1, Ordering::Acquire); Ok(Arc::new(MacInterface { @@ -357,19 +396,20 @@ impl MacInterface { let mut state = self.state.lock().unwrap(); if !state.endpoints_used.is_empty() { - // TODO: Use ErrorKind::ResourceBusy once compatible with MSRV - - return Err(Error::other( - "must drop endpoints before changing alt setting", + return Err(Error::new( + ErrorKind::Busy, + "can't change alternate setting while endpoints are in use", )); } - unsafe { - check_iokit_return(call_iokit_function!( - self.interface.raw, - SetAlternateInterface(alt_setting) - ))?; - } + self.interface + .set_alternate_interface(alt_setting) + .map_err(|e| match e { + io_kit_sys::ret::kIOReturnNoDevice => { + Error::new_os(ErrorKind::Disconnected, "device disconnected", e) + } + _ => Error::new_os(ErrorKind::Other, "failed to set alternate interface", e), + })?; debug!( "Set interface {} alt setting to {alt_setting}", @@ -405,7 +445,7 @@ impl MacInterface { pub fn endpoint( self: &Arc, descriptor: EndpointDescriptor, - ) -> Result { + ) -> Result { let address = descriptor.address(); let max_packet_size = descriptor.max_packet_size(); @@ -413,11 +453,14 @@ impl MacInterface { let Some(pipe_ref) = self.interface.find_pipe_ref(address) else { debug!("Endpoint {address:02X} not found in iokit"); - return Err(ClaimEndpointError::InvalidAddress); + return Err(Error::new( + ErrorKind::NotFound, + "specified endpoint does not exist on IOKit interface", + )); }; if state.endpoints_used.is_set(address) { - return Err(ClaimEndpointError::Busy); + return Err(Error::new(ErrorKind::Busy, "endpoint already in use")); } state.endpoints_used.set(address); @@ -589,12 +632,16 @@ impl MacEndpoint { Blocking::new(move || { debug!("Clear halt, endpoint {:02x}", inner.address); - unsafe { - check_iokit_return(call_iokit_function!( - inner.interface.interface.raw, - ClearPipeStallBothEnds(inner.pipe_ref) - )) - } + inner + .interface + .interface + .clear_pipe_stall_both_ends(inner.pipe_ref) + .map_err(|e| match e { + io_kit_sys::ret::kIOReturnNoDevice => { + Error::new_os(ErrorKind::Disconnected, "device disconnected", e) + } + _ => Error::new_os(ErrorKind::Other, "failed to clear halt on endpoint", e), + }) }) } } diff --git a/src/platform/macos_iokit/enumeration.rs b/src/platform/macos_iokit/enumeration.rs index c9aba92..7ba0bdf 100644 --- a/src/platform/macos_iokit/enumeration.rs +++ b/src/platform/macos_iokit/enumeration.rs @@ -1,5 +1,3 @@ -use std::io::ErrorKind; - use core_foundation::{ base::{CFType, TCFType}, data::CFData, @@ -18,7 +16,7 @@ use log::debug; use crate::{ descriptors::DeviceDescriptor, maybe_future::{MaybeFuture, Ready}, - BusInfo, DeviceInfo, Error, InterfaceInfo, Speed, UsbControllerType, + BusInfo, DeviceInfo, Error, ErrorKind, InterfaceInfo, Speed, UsbControllerType, }; use super::iokit::{IoService, IoServiceIterator}; @@ -43,13 +41,17 @@ fn usb_service_iter() -> Result { unsafe { let dictionary = IOServiceMatching(kIOUSBDeviceClassName); if dictionary.is_null() { - return Err(Error::other("IOServiceMatching failed")); + return Err(Error::new(ErrorKind::Other, "IOServiceMatching failed")); } let mut iterator = 0; let r = IOServiceGetMatchingServices(kIOMasterPortDefault, dictionary, &mut iterator); if r != kIOReturnSuccess { - return Err(Error::from_raw_os_error(r)); + return Err(Error::new_os( + ErrorKind::Other, + "failed to create IOKit iterator", + r, + )); } Ok(IoServiceIterator::new(iterator)) @@ -67,13 +69,17 @@ fn usb_controller_service_iter( UsbControllerType::VHCI => IOServiceMatching(kAppleUSBVHCI), }; if dictionary.is_null() { - return Err(Error::other("IOServiceMatching failed")); + return Err(Error::new(ErrorKind::Other, "IOServiceMatching failed")); } let mut iterator = 0; let r = IOServiceGetMatchingServices(kIOMasterPortDefault, dictionary, &mut iterator); if r != kIOReturnSuccess { - return Err(Error::from_raw_os_error(r)); + return Err(Error::new_os( + ErrorKind::Other, + "failed to create IOKit iterator", + r, + )); } Ok(IoServiceIterator::new(iterator)) @@ -245,7 +251,11 @@ fn get_children(device: &IoService) -> Result { IORegistryEntryGetChildIterator(device.get(), kIOServicePlane as *mut _, &mut iterator); if r != kIOReturnSuccess { debug!("IORegistryEntryGetChildIterator failed: {r}"); - return Err(Error::from_raw_os_error(r)); + return Err(Error::new_os( + ErrorKind::Other, + "failed to create IOKit child iterator", + r, + )); } Ok(IoServiceIterator::new(iterator)) diff --git a/src/platform/macos_iokit/hotplug.rs b/src/platform/macos_iokit/hotplug.rs index f95444b..f9e5f53 100644 --- a/src/platform/macos_iokit/hotplug.rs +++ b/src/platform/macos_iokit/hotplug.rs @@ -18,7 +18,7 @@ use io_kit_sys::{ use log::debug; use slab::Slab; -use crate::{hotplug::HotplugEvent, DeviceId, Error}; +use crate::{hotplug::HotplugEvent, DeviceId, Error, ErrorKind}; use super::{ enumeration::{get_registry_id, probe_device}, @@ -79,7 +79,7 @@ impl MacHotplugWatch { let dictionary = unsafe { let d = IOServiceMatching(kIOUSBDeviceClassName); if d.is_null() { - return Err(Error::other("IOServiceMatching failed")); + return Err(Error::new(ErrorKind::Other, "IOServiceMatching failed")); } CFDictionary::wrap_under_create_rule(d) }; @@ -162,7 +162,9 @@ fn register_notification( ); if r != kIOReturnSuccess { - return Err(Error::other("Failed to register notification")); + return Err( + Error::new_os(ErrorKind::Other, "failed to register notification", r).log_error(), + ); } let mut iter = IoServiceIterator::new(iter); diff --git a/src/platform/macos_iokit/iokit.rs b/src/platform/macos_iokit/iokit.rs index 3a785aa..9eff110 100644 --- a/src/platform/macos_iokit/iokit.rs +++ b/src/platform/macos_iokit/iokit.rs @@ -5,9 +5,6 @@ use core_foundation_sys::uuid::CFUUIDBytes; use io_kit_sys::{ret::IOReturn, IOIteratorNext, IOObjectRelease}; -use std::io::ErrorKind; - -use crate::Error; use super::iokit_c::{self, CFUUIDGetUUIDBytes, IOCFPlugInInterface}; @@ -117,15 +114,9 @@ pub(crate) fn usb_interface_type_id() -> CFUUIDBytes { unsafe { CFUUIDGetUUIDBytes(iokit_c::kIOUSBInterfaceInterfaceID500()) } } -pub(crate) fn check_iokit_return(r: IOReturn) -> Result<(), Error> { - #[allow(non_upper_case_globals)] - #[deny(unreachable_patterns)] +pub(crate) fn check_iokit_return(r: IOReturn) -> Result<(), IOReturn> { match r { io_kit_sys::ret::kIOReturnSuccess => Ok(()), - io_kit_sys::ret::kIOReturnExclusiveAccess => { - Err(Error::other("could not be opened for exclusive access")) - } - io_kit_sys::ret::kIOReturnNotFound => Err(Error::new(ErrorKind::NotFound, "not found")), - _ => Err(Error::from_raw_os_error(r)), + e => Err(e), } } diff --git a/src/platform/macos_iokit/iokit_usb.rs b/src/platform/macos_iokit/iokit_usb.rs index 5a5c9f7..315d3f3 100644 --- a/src/platform/macos_iokit/iokit_usb.rs +++ b/src/platform/macos_iokit/iokit_usb.rs @@ -3,12 +3,12 @@ //! Based on Kate Temkin's [usrs](https://github.com/ktemkin/usrs) //! licensed under MIT OR Apache-2.0. -use std::{io::ErrorKind, ptr, slice, time::Duration}; +use std::{ptr, slice, time::Duration}; use core_foundation::{base::TCFType, runloop::CFRunLoopSource}; use core_foundation_sys::runloop::CFRunLoopSourceRef; use io_kit_sys::{ - ret::{kIOReturnNoResources, kIOReturnSuccess}, + ret::{kIOReturnNoResources, kIOReturnSuccess, IOReturn}, types::io_iterator_t, }; use log::error; @@ -17,7 +17,7 @@ use crate::{ platform::macos_iokit::{ iokit::usb_interface_type_id, iokit_c::kIOUsbInterfaceUserClientTypeID, }, - Error, + Error, ErrorKind, }; use super::{ @@ -75,12 +75,15 @@ impl IoKitDevice { } if rc != kIOReturnSuccess { - return Err(Error::from_raw_os_error(rc)); + return Err(Error::new_os(ErrorKind::Other, "failed to open device", rc)); } if raw_device_plugin.is_null() { error!("IOKit indicated it successfully created a PlugInInterface, but the pointer was NULL"); - return Err(Error::other("Could not create PlugInInterface")); + return Err(Error::new( + ErrorKind::Other, + "could not create PlugInInterface", + )); } let device_plugin = PluginInterface::new(raw_device_plugin); @@ -113,7 +116,24 @@ impl IoKitDevice { } } - pub(crate) fn create_async_event_source(&self) -> Result { + pub(crate) fn open(&self) -> Result<(), IOReturn> { + unsafe { check_iokit_return(call_iokit_function!(self.raw, USBDeviceOpen())) } + } + + pub(crate) fn set_configuration(&self, configuration: u8) -> Result<(), IOReturn> { + unsafe { + check_iokit_return(call_iokit_function!( + self.raw, + SetConfiguration(configuration) + )) + } + } + + pub(crate) fn reset(&self) -> Result<(), IOReturn> { + unsafe { check_iokit_return(call_iokit_function!(self.raw, USBDeviceReEnumerate(0))) } + } + + pub(crate) fn create_async_event_source(&self) -> Result { unsafe { let mut raw_source: CFRunLoopSourceRef = std::ptr::null_mut(); check_iokit_return(call_iokit_function!( @@ -125,7 +145,7 @@ impl IoKitDevice { } /// Returns an IOKit iterator that can be used to iterate over all interfaces on this device. - pub(crate) fn create_interface_iterator(&self) -> Result { + pub(crate) fn create_interface_iterator(&self) -> Result { unsafe { let mut iterator: io_iterator_t = 0; @@ -145,7 +165,7 @@ impl IoKitDevice { } } - pub(crate) fn get_number_of_configurations(&self) -> Result { + pub(crate) fn get_number_of_configurations(&self) -> Result { unsafe { let mut num = 0; check_iokit_return(call_iokit_function!( @@ -156,7 +176,7 @@ impl IoKitDevice { } } - pub(crate) fn get_configuration_descriptor(&self, index: u8) -> Result<&[u8], Error> { + pub(crate) fn get_configuration_descriptor(&self, index: u8) -> Result<&[u8], IOReturn> { unsafe { let mut ptr: *mut IOUSBConfigurationDescriptor = ptr::null_mut(); check_iokit_return(call_iokit_function!( @@ -168,7 +188,7 @@ impl IoKitDevice { } } - pub(crate) fn get_configuration(&self) -> Result { + pub(crate) fn get_configuration(&self) -> Result { unsafe { let mut val = 0; check_iokit_return(call_iokit_function!(self.raw, GetConfiguration(&mut val)))?; @@ -208,12 +228,19 @@ impl IoKitInterface { ); if rc != kIOReturnSuccess { - return Err(Error::from_raw_os_error(rc)); + return Err(Error::new_os( + ErrorKind::Other, + "failed to open interface", + rc, + )); } if raw_interface_plugin.is_null() { error!("IOKit indicated it successfully created a PlugInInterface, but the pointer was NULL"); - return Err(Error::other("Could not create PlugInInterface")); + return Err(Error::new( + ErrorKind::Other, + "could not create PlugInInterface", + )); } let interface_plugin = PluginInterface::new(raw_interface_plugin); @@ -238,15 +265,15 @@ impl IoKitInterface { } } - pub(crate) fn open(&mut self) -> Result<(), Error> { + pub(crate) fn open(&mut self) -> Result<(), IOReturn> { unsafe { check_iokit_return(call_iokit_function!(self.raw, USBInterfaceOpen())) } } - pub(crate) fn close(&mut self) -> Result<(), Error> { + pub(crate) fn close(&mut self) -> Result<(), IOReturn> { unsafe { check_iokit_return(call_iokit_function!(self.raw, USBInterfaceClose())) } } - pub(crate) fn create_async_event_source(&self) -> Result { + pub(crate) fn create_async_event_source(&self) -> Result { unsafe { let mut raw_source: CFRunLoopSourceRef = std::ptr::null_mut(); check_iokit_return(call_iokit_function!( @@ -291,6 +318,24 @@ impl IoKitInterface { None } } + + pub(crate) fn set_alternate_interface(&self, alt_setting: u8) -> Result<(), IOReturn> { + unsafe { + check_iokit_return(call_iokit_function!( + self.raw, + SetAlternateInterface(alt_setting) + )) + } + } + + pub(crate) fn clear_pipe_stall_both_ends(&self, pipe_ref: u8) -> Result<(), IOReturn> { + unsafe { + check_iokit_return(call_iokit_function!( + self.raw, + ClearPipeStallBothEnds(pipe_ref) + )) + } + } } impl Drop for IoKitInterface { diff --git a/src/platform/macos_iokit/mod.rs b/src/platform/macos_iokit/mod.rs index 4cc46fc..f2c53f4 100644 --- a/src/platform/macos_iokit/mod.rs +++ b/src/platform/macos_iokit/mod.rs @@ -1,4 +1,7 @@ +use std::num::NonZeroU32; + use crate::transfer::TransferError; +use crate::ErrorKind; mod transfer; use io_kit_sys::ret::IOReturn; @@ -37,3 +40,17 @@ fn status_to_transfer_result(status: IOReturn) -> Result<(), TransferError> { _ => Err(TransferError::Unknown(status as u32)), } } + +pub fn format_os_error_code(f: &mut std::fmt::Formatter<'_>, code: u32) -> std::fmt::Result { + write!(f, "error 0x{:08x}", code) +} + +impl crate::error::Error { + pub(crate) fn new_os(kind: ErrorKind, message: &'static str, code: IOReturn) -> Self { + Self { + kind, + code: NonZeroU32::new(code as u32), + message, + } + } +} diff --git a/src/platform/windows_winusb/device.rs b/src/platform/windows_winusb/device.rs index ef4309a..643eb14 100644 --- a/src/platform/windows_winusb/device.rs +++ b/src/platform/windows_winusb/device.rs @@ -1,7 +1,7 @@ use std::{ collections::{btree_map::Entry, BTreeMap, VecDeque}, ffi::c_void, - io::{self, ErrorKind}, + io, mem::{size_of_val, transmute}, os::windows::{ io::{AsRawHandle, RawHandle}, @@ -23,7 +23,8 @@ use windows_sys::Win32::{ }, Foundation::{ GetLastError, ERROR_BAD_COMMAND, ERROR_DEVICE_NOT_CONNECTED, ERROR_FILE_NOT_FOUND, - ERROR_IO_PENDING, ERROR_NOT_FOUND, ERROR_NO_SUCH_DEVICE, FALSE, HANDLE, TRUE, + ERROR_IO_PENDING, ERROR_NOT_FOUND, ERROR_NO_MORE_ITEMS, ERROR_NO_SUCH_DEVICE, FALSE, + HANDLE, TRUE, }, System::IO::{CancelIoEx, OVERLAPPED}, }; @@ -34,7 +35,6 @@ use crate::{ ConfigurationDescriptor, DeviceDescriptor, EndpointDescriptor, DESCRIPTOR_LEN_DEVICE, DESCRIPTOR_TYPE_CONFIGURATION, }, - device::ClaimEndpointError, maybe_future::{blocking::Blocking, Ready}, transfer::{ internal::{ @@ -42,7 +42,7 @@ use crate::{ }, Buffer, Completion, ControlIn, ControlOut, Direction, Recipient, TransferError, }, - DeviceInfo, Error, MaybeFuture, Speed, + DeviceInfo, Error, ErrorKind, MaybeFuture, Speed, }; use super::{ @@ -87,7 +87,7 @@ impl WindowsDevice { ) }; let device_descriptor = DeviceDescriptor::new(device_descriptor) - .ok_or_else(|| Error::new(ErrorKind::InvalidData, "invalid device descriptor"))?; + .ok_or_else(|| Error::new(ErrorKind::Other, "invalid device descriptor"))?; let num_configurations = connection_info.device_desc.bNumConfigurations; let config_descriptors = (0..num_configurations) @@ -136,7 +136,7 @@ impl WindowsDevice { &self, _configuration: u8, ) -> impl MaybeFuture> { - Ready(Err(io::Error::new( + Ready(Err(Error::new( ErrorKind::Unsupported, "set_configuration not supported by WinUSB", ))) @@ -147,18 +147,24 @@ impl WindowsDevice { desc_type: u8, desc_index: u8, language_id: u16, - ) -> impl MaybeFuture, Error>> { + ) -> impl MaybeFuture, TransferError>> { Blocking::new(move || { - HubPort::by_child_devinst(self.devinst)?.get_descriptor( - desc_type, - desc_index, - language_id, - ) + fn to_transfer_error(e: Error) -> TransferError { + match e.kind() { + ErrorKind::Disconnected => TransferError::Disconnected, + _ => TransferError::Unknown(e.os_error().unwrap_or(0)), + } + } + + HubPort::by_child_devinst(self.devinst) + .map_err(to_transfer_error)? + .get_descriptor(desc_type, desc_index, language_id) + .map_err(to_transfer_error) }) } pub(crate) fn reset(&self) -> impl MaybeFuture> { - Ready(Err(io::Error::new( + Ready(Err(Error::new( ErrorKind::Unsupported, "reset not supported by WinUSB", ))) @@ -204,9 +210,10 @@ impl WindowsDevice { } } } else { + debug!("Device driver is {driver:?}, not WinUSB or USBCCGP"); Err(Error::new( ErrorKind::Unsupported, - format!("Device driver is {driver:?}, not WinUSB or USBCCGP"), + "incompatible driver is installed for this device", )) } }) @@ -266,14 +273,19 @@ unsafe impl Sync for WinusbFileHandle {} impl WinusbFileHandle { fn new(path: &WCStr, first_interface: u8) -> Result { - let handle = create_file(path)?; + let handle = create_file(path) + .map_err(|e| Error::new_os(ErrorKind::Other, "failed to open device", e).log_debug())?; super::events::register(&handle)?; let winusb_handle = unsafe { let mut h = ptr::null_mut(); if WinUsb_Initialize(raw_handle(&handle), &mut h) == FALSE { - error!("WinUsb_Initialize failed: {:?}", io::Error::last_os_error()); - return Err(io::Error::last_os_error()); + return Err(Error::new_os( + ErrorKind::Other, + "failed to initialize WinUSB", + GetLastError(), + ) + .log_debug()); } h }; @@ -296,10 +308,7 @@ impl WinusbFileHandle { assert!(interface_number >= self.first_interface); if self.claimed_interfaces.is_set(interface_number) { - return Err(Error::new( - ErrorKind::AddrInUse, - "Interface is already claimed", - )); + Error::new(ErrorKind::Busy, "interface is already claimed"); } let winusb_handle = if self.first_interface == interface_number { @@ -310,13 +319,22 @@ impl WinusbFileHandle { let idx = interface_number - self.first_interface - 1; if WinUsb_GetAssociatedInterface(self.winusb_handle, idx, &mut out_handle) == FALSE { + let err = GetLastError(); error!( "WinUsb_GetAssociatedInterface for {} on {} failed: {:?}", - interface_number, - self.first_interface, - io::Error::last_os_error() + interface_number, self.first_interface, err ); - return Err(io::Error::last_os_error()); + + return Err(match err { + ERROR_NO_MORE_ITEMS => { + Error::new_os(ErrorKind::NotFound, "interface not found", err) + } + _ => Error::new_os( + ErrorKind::Other, + "failed to initialize WinUSB for associated interface", + err, + ), + }); } out_handle } @@ -463,9 +481,9 @@ impl WindowsInterface { Blocking::new(move || unsafe { let mut state = self.state.lock().unwrap(); if !state.endpoints.is_empty() { - // TODO: Use ErrorKind::ResourceBusy once compatible with MSRV - return Err(Error::other( - "must drop endpoints before changing alt setting", + return Err(Error::new( + ErrorKind::Busy, + "can't change alternate setting while endpoints are in use", )); } let r = WinUsb_SetCurrentAlternateSetting(self.winusb_handle, alt_setting); @@ -477,7 +495,15 @@ impl WindowsInterface { state.alt_setting = alt_setting; Ok(()) } else { - Err(io::Error::last_os_error()) + Err(match GetLastError() { + e @ ERROR_NOT_FOUND => { + Error::new_os(ErrorKind::NotFound, "alternate setting not found", e) + } + e @ ERROR_BAD_COMMAND => { + Error::new_os(ErrorKind::Disconnected, "device disconnected", e) + } + e => Error::new_os(ErrorKind::Other, "failed to set alternate setting", e), + }) } }) } @@ -489,14 +515,14 @@ impl WindowsInterface { pub fn endpoint( self: &Arc, descriptor: EndpointDescriptor, - ) -> Result { + ) -> Result { let address = descriptor.address(); let max_packet_size = descriptor.max_packet_size(); let mut state = self.state.lock().unwrap(); if state.endpoints.is_set(address) { - return Err(ClaimEndpointError::Busy); + return Err(Error::new(ErrorKind::Busy, "endpoint already in use")); } state.endpoints.set(address); @@ -730,11 +756,12 @@ impl WindowsEndpoint { let endpoint = inner.address; debug!("Clear halt, endpoint {endpoint:02x}"); unsafe { - let r = WinUsb_ResetPipe(inner.interface.winusb_handle, endpoint); - if r == TRUE { + if WinUsb_ResetPipe(inner.interface.winusb_handle, endpoint) == TRUE { Ok(()) } else { - Err(io::Error::last_os_error()) + Err(match GetLastError() { + e => Error::new_os(ErrorKind::Other, "failed to clear halt", e), + }) } } }) diff --git a/src/platform/windows_winusb/enumeration.rs b/src/platform/windows_winusb/enumeration.rs index a328570..95e2292 100644 --- a/src/platform/windows_winusb/enumeration.rs +++ b/src/platform/windows_winusb/enumeration.rs @@ -1,7 +1,4 @@ -use std::{ - ffi::{OsStr, OsString}, - io::ErrorKind, -}; +use std::ffi::{OsStr, OsString}; use log::debug; use windows_sys::Win32::Devices::{ @@ -19,7 +16,7 @@ use crate::{ DESCRIPTOR_TYPE_CONFIGURATION, DESCRIPTOR_TYPE_STRING, }, maybe_future::{blocking::Blocking, MaybeFuture}, - BusInfo, DeviceInfo, Error, InterfaceInfo, UsbControllerType, + BusInfo, DeviceInfo, Error, ErrorKind, InterfaceInfo, UsbControllerType, }; use super::{ @@ -241,7 +238,10 @@ pub(crate) fn get_winusb_device_path(dev: DevInst) -> Result { let paths = dev.interfaces(GUID_DEVINTERFACE_USB_DEVICE); let Some(path) = paths.iter().next() else { - return Err(Error::other("Failed to find device path for WinUSB device")); + return Err(Error::new( + ErrorKind::Other, + "failed to find device path for WinUSB device", + )); }; Ok(path.to_owned()) @@ -266,14 +266,15 @@ pub(crate) fn get_usbccgp_winusb_device_path(child: DevInst) -> Result(DEVPKEY_Device_Service) else { return Err(Error::new( ErrorKind::Unsupported, - "Could not determine driver for interface", + "could not determine driver for interface", )); }; if !driver.eq_ignore_ascii_case("winusb") { + debug!("Incompatible driver {driver:?} for interface, not WinUSB"); return Err(Error::new( ErrorKind::Unsupported, - format!("Interface driver is {driver:?}, not WinUSB"), + "incompatible driver is installed for this interface", )); } @@ -298,9 +299,11 @@ pub(crate) fn get_usbccgp_winusb_device_path(child: DevInst) -> Result Ok(IoCompletionPort(handle)), - Err(_) => { - let err = GetLastError(); - error!("CreateIoCompletionPort (create) failed: {err:?}"); - Err(std::io::Error::from_raw_os_error(err as i32)) - } + Err(_) => Err(Error::new_os( + crate::ErrorKind::Other, + "failed to create IO completion port", + GetLastError(), + ) + .log_error()), } } } @@ -37,9 +37,12 @@ impl IoCompletionPort { unsafe { let r = CreateIoCompletionPort(raw_handle(handle), raw_handle(&self.0), 0, 0); if r.is_null() { - let err = std::io::Error::last_os_error(); - error!("CreateIoCompletionPort (register) failed: {err:?}"); - Err(err) + Err(Error::new_os( + crate::ErrorKind::Other, + "failed to register IO completion port", + GetLastError(), + ) + .log_error()) } else { Ok(()) } @@ -62,9 +65,12 @@ impl IoCompletionPort { ); if r == FALSE { - let err = std::io::Error::last_os_error(); - error!("GetQueuedCompletionStatusEx failed: {err:?}"); - Err(err) + Err(Error::new_os( + crate::ErrorKind::Other, + "failed to get events from IO completion port", + GetLastError(), + ) + .log_error()) } else { events.set_len(event_count as usize); Ok(()) diff --git a/src/platform/windows_winusb/hotplug.rs b/src/platform/windows_winusb/hotplug.rs index 491d0ad..f1be59c 100644 --- a/src/platform/windows_winusb/hotplug.rs +++ b/src/platform/windows_winusb/hotplug.rs @@ -7,7 +7,7 @@ use std::{ task::{Context, Poll, Waker}, }; -use log::{debug, error}; +use log::debug; use windows_sys::Win32::{ Devices::{ DeviceAndDriverInstallation::{ @@ -78,8 +78,12 @@ impl WindowsHotplugWatch { }; if cr != CR_SUCCESS { - error!("CM_Register_Notification failed: {cr}"); - return Err(Error::other("Failed to initialize hotplug notifications")); + return Err(Error::new_os( + crate::ErrorKind::Other, + "failed to initialize hotplug notifications", + cr, + ) + .log_error()); } Ok(WindowsHotplugWatch { diff --git a/src/platform/windows_winusb/hub.rs b/src/platform/windows_winusb/hub.rs index 16acc6b..fd88667 100644 --- a/src/platform/windows_winusb/hub.rs +++ b/src/platform/windows_winusb/hub.rs @@ -1,7 +1,6 @@ use std::{ alloc::{self, Layout}, ffi::c_void, - io::ErrorKind, mem, os::windows::prelude::OwnedHandle, ptr::{addr_of, null_mut}, @@ -25,6 +24,13 @@ use windows_sys::Win32::{ System::IO::DeviceIoControl, }; +use crate::{descriptors::DESCRIPTOR_TYPE_DEVICE, Error, ErrorKind, Speed}; + +use super::{ + cfgmgr32::DevInst, + util::{create_file, raw_handle}, +}; + // flags for USB_NODE_CONNECTION_INFORMATION_EX_V2.SupportedUsbProtocols const USB110: u32 = 0x01; const USB200: u32 = 0x02; @@ -36,13 +42,6 @@ const DEVICE_IS_SUPER_SPEED_CAPABLE_OR_HIGHER: u32 = 0x02; const DEVICE_IS_OPERATING_AT_SUPER_SPEED_PLUS_OR_HIGHER: u32 = 0x04; const DEVICE_IS_SUPER_SPEED_PLUS_CAPABLE_OR_HIGHER: u32 = 0x08; -use crate::{descriptors::DESCRIPTOR_TYPE_DEVICE, Error, Speed}; - -use super::{ - cfgmgr32::DevInst, - util::{create_file, raw_handle}, -}; - /// Safe wrapper around hub ioctls used to get descriptors for child devices. pub struct HubHandle(OwnedHandle); @@ -83,19 +82,25 @@ impl HubHandle { ); if r == TRUE { + if info.ConnectionStatus != windows_sys::Win32::Devices::Usb::DeviceConnected { + return Err(Error::new(ErrorKind::Disconnected, "device disconnected")); + } if info.DeviceDescriptor.bDescriptorType != DESCRIPTOR_TYPE_DEVICE { // When the device is disconnected during this call, Windows is observed to // sometimes return an all-zero device descriptor. - return Err(Error::other( + return Err(Error::new(ErrorKind::Other, "IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX returned an invalid device descriptor", )); } Ok(info) } else { - let err = Error::last_os_error(); - debug!("Hub DeviceIoControl failed: {err:?}"); - Err(err) + Err(Error::new_os( + ErrorKind::Other, + "hub DeviceIoControl failed", + GetLastError(), + ) + .log_debug()) } } } @@ -124,9 +129,12 @@ impl HubHandle { if r == TRUE { Ok(info) } else { - let err = Error::last_os_error(); - debug!("Hub DeviceIoControl failed: {err:?}"); - Err(err) + Err(Error::new_os( + ErrorKind::Other, + "hub DeviceIoControl failed", + GetLastError(), + ) + .log_debug()) } } } @@ -186,10 +194,12 @@ impl HubHandle { let err = GetLastError(); debug!("IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION failed: type={descriptor_type} index={descriptor_index} error={err:?}"); Err(match err { - ERROR_GEN_FAILURE => { - Error::other("Descriptor request failed. Device might be suspended.") - } - _ => Error::from_raw_os_error(err as i32), + ERROR_GEN_FAILURE => Error::new_os( + ErrorKind::Other, + "descriptor request failed: device might be suspended.", + err, + ), + _ => Error::new_os(ErrorKind::Other, "descriptor request failed", err), }) }; @@ -215,12 +225,12 @@ impl HubPort { pub fn by_child_devinst(devinst: DevInst) -> Result { let parent_hub = devinst .parent() - .ok_or_else(|| Error::other("failed to find parent hub"))?; + .ok_or_else(|| Error::new(ErrorKind::Disconnected, "failed to find parent hub"))?; let hub_handle = HubHandle::by_devinst(parent_hub) - .ok_or_else(|| Error::other("failed to open parent hub"))?; + .ok_or_else(|| Error::new(ErrorKind::Disconnected, "failed to open parent hub"))?; let Some(port_number) = devinst.get_property::(DEVPKEY_Device_Address) else { return Err(Error::new( - ErrorKind::NotConnected, + ErrorKind::Disconnected, "Could not find hub port number", )); }; diff --git a/src/platform/windows_winusb/mod.rs b/src/platform/windows_winusb/mod.rs index f63c8d0..f7861d0 100644 --- a/src/platform/windows_winusb/mod.rs +++ b/src/platform/windows_winusb/mod.rs @@ -1,4 +1,6 @@ mod enumeration; +use std::num::NonZeroU32; + pub use enumeration::{list_buses, list_devices}; mod events; @@ -14,7 +16,24 @@ mod cfgmgr32; mod hub; mod registry; pub(crate) use cfgmgr32::DevInst; +use windows_sys::Win32::Foundation::WIN32_ERROR; pub(crate) use DevInst as DeviceId; mod hotplug; mod util; pub(crate) use hotplug::WindowsHotplugWatch as HotplugWatch; + +use crate::ErrorKind; + +pub fn format_os_error_code(f: &mut std::fmt::Formatter<'_>, code: u32) -> std::fmt::Result { + write!(f, "error {}", code) +} + +impl crate::error::Error { + pub(crate) fn new_os(kind: ErrorKind, message: &'static str, code: WIN32_ERROR) -> Self { + Self { + kind, + code: NonZeroU32::new(code as u32), + message, + } + } +} diff --git a/src/platform/windows_winusb/registry.rs b/src/platform/windows_winusb/registry.rs index 4ca6650..55403b1 100644 --- a/src/platform/windows_winusb/registry.rs +++ b/src/platform/windows_winusb/registry.rs @@ -1,7 +1,6 @@ use std::{ alloc::{self, Layout}, ffi::OsStr, - io::ErrorKind, mem, ptr::{null, null_mut}, }; @@ -9,7 +8,7 @@ use std::{ use windows_sys::{ core::GUID, Win32::{ - Foundation::{ERROR_SUCCESS, S_OK}, + Foundation::{GetLastError, ERROR_SUCCESS, S_OK}, System::{ Com::IIDFromString, Registry::{RegCloseKey, RegQueryValueExW, HKEY, REG_MULTI_SZ, REG_SZ}, @@ -45,13 +44,18 @@ impl RegKey { ); if r != ERROR_SUCCESS { - return Err(Error::from_raw_os_error(r as i32)); + return Err(Error::new_os( + crate::ErrorKind::Other, + "failed to read registry value", + GetLastError(), + )); } if ty != REG_MULTI_SZ && ty != REG_SZ { - return Err(Error::new( - ErrorKind::InvalidInput, - "registry value type not string", + return Err(Error::new_os( + crate::ErrorKind::Other, + "failed to read registry value: expected string", + GetLastError(), )); } @@ -63,7 +67,11 @@ impl RegKey { if r != ERROR_SUCCESS { alloc::dealloc(buf, layout); - return Err(Error::from_raw_os_error(r as i32)); + return Err(Error::new_os( + crate::ErrorKind::Other, + "failed to read registry value data", + GetLastError(), + )); } let mut guid = GUID::from_u128(0); @@ -74,7 +82,10 @@ impl RegKey { if r == S_OK { Ok(guid) } else { - Err(Error::new(ErrorKind::InvalidData, "invalid UUID")) + Err(Error::new( + crate::ErrorKind::Other, + "failed to parse GUID from registry value", + )) } } } diff --git a/src/platform/windows_winusb/util.rs b/src/platform/windows_winusb/util.rs index bf98da3..151e41a 100644 --- a/src/platform/windows_winusb/util.rs +++ b/src/platform/windows_winusb/util.rs @@ -2,7 +2,6 @@ use std::{ borrow::Borrow, ffi::{OsStr, OsString}, fmt::{Display, Write}, - io, ops::Deref, os::windows::prelude::{ AsHandle, AsRawHandle, HandleOrInvalid, OsStrExt, OsStringExt, OwnedHandle, RawHandle, @@ -12,14 +11,14 @@ use std::{ }; use windows_sys::Win32::{ - Foundation::{GENERIC_READ, GENERIC_WRITE, HANDLE}, + Foundation::{GetLastError, GENERIC_READ, GENERIC_WRITE, HANDLE, WIN32_ERROR}, Storage::FileSystem::{ CreateFileW, FILE_FLAG_OVERLAPPED, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING, }, }; /// Wrapper around `CreateFile` -pub fn create_file(path: &WCStr) -> Result { +pub fn create_file(path: &WCStr) -> Result { unsafe { let r = CreateFileW( path.as_ptr(), @@ -32,7 +31,7 @@ pub fn create_file(path: &WCStr) -> Result { ); HandleOrInvalid::from_raw_handle(r as RawHandle) .try_into() - .map_err(|_| io::Error::last_os_error()) + .map_err(|_| GetLastError()) } } diff --git a/src/transfer/mod.rs b/src/transfer/mod.rs index 6ead6f0..1f5fcbc 100644 --- a/src/transfer/mod.rs +++ b/src/transfer/mod.rs @@ -16,7 +16,7 @@ pub use buffer::Buffer; pub(crate) mod internal; -use crate::descriptors::TransferType; +use crate::{descriptors::TransferType, platform}; /// Transfer error. #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -60,11 +60,9 @@ impl Display for TransferError { TransferError::Fault => write!(f, "hardware fault or protocol violation"), TransferError::InvalidArgument => write!(f, "invalid or unsupported argument"), TransferError::Unknown(e) => { - if cfg!(target_os = "macos") { - write!(f, "unknown error (0x{e:08x})") - } else { - write!(f, "unknown error ({e})") - } + write!(f, "unknown (")?; + platform::format_os_error_code(f, *e)?; + write!(f, ")") } } }