get_last_error/
lib.rs

1#![cfg(windows)]
2#![cfg_attr(not(feature = "std"), no_std)]
3#![warn(
4    unsafe_op_in_unsafe_fn,
5    missing_docs,
6    missing_debug_implementations,
7    missing_copy_implementations,
8    rust_2018_idioms,
9    clippy::todo,
10    clippy::manual_assert,
11    clippy::must_use_candidate,
12    clippy::inconsistent_struct_constructor,
13    clippy::wrong_self_convention,
14    clippy::missing_const_for_fn,
15    rustdoc::broken_intra_doc_links,
16    rustdoc::private_intra_doc_links
17)]
18
19//! # get-last-error
20//!
21//! An error wrapper over Win32 API errors.
22//!
23//! ## Examples
24//!
25//! A [`Win32Error`] can be constructed from an arbitrary [`DWORD`]:
26//!
27//! ```
28//! use get_last_error::Win32Error;
29//!
30//! let err = Win32Error::new(0);
31//! println!("{}", err); // prints "The operation completed successfully."
32//! ```
33//!
34//! The [`Win32Error::get_last_error`] retrieves the last error code for the current thread:
35//!
36//! ```
37//! use get_last_error::Win32Error;
38//! use winapi::um::{winnt::HANDLE, processthreadsapi::OpenProcess};
39//!
40//! fn open_process() -> Result<HANDLE, Win32Error> {
41//!     let result = unsafe { OpenProcess(0, 0, 0) }; // some windows api call
42//!     if result.is_null() { // null indicates failure.
43//!         Err(Win32Error::get_last_error())
44//!     } else {
45//!         Ok(result)
46//!     }
47//! }
48//! ```
49
50use core::{
51    fmt::{self, Display, Write},
52    mem::MaybeUninit,
53    ptr,
54};
55
56#[cfg(feature = "std")]
57use std::{error::Error, io};
58
59use winapi::{
60    shared::minwindef::DWORD,
61    um::{
62        errhandlingapi::GetLastError,
63        winbase::{
64            FormatMessageW, FORMAT_MESSAGE_FROM_SYSTEM, FORMAT_MESSAGE_IGNORE_INSERTS,
65            FORMAT_MESSAGE_MAX_WIDTH_MASK,
66        },
67    },
68};
69
70/// A wrapper over Win32 API errors.
71/// Implements [`Display`] using [`FormatMessageW`](https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-formatmessagew).
72#[repr(transparent)]
73#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
74pub struct Win32Error(DWORD);
75
76impl Win32Error {
77    /// Constructs a new error from an arbitrary [`DWORD`].
78    #[must_use]
79    pub const fn new(code: DWORD) -> Self {
80        Self(code)
81    }
82
83    /// Returns the last error code for the current thread.
84    #[must_use]
85    pub fn get_last_error() -> Self {
86        Self::new(unsafe { GetLastError() })
87    }
88
89    /// Returns the underlying error code.
90    #[must_use]
91    pub const fn code(&self) -> DWORD {
92        self.0
93    }
94}
95
96impl From<DWORD> for Win32Error {
97    fn from(other: DWORD) -> Self {
98        Self::new(other)
99    }
100}
101
102impl From<Win32Error> for DWORD {
103    fn from(other: Win32Error) -> Self {
104        other.code()
105    }
106}
107
108impl Display for Win32Error {
109    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110        let mut buf = maybe_uninit_uninit_array::<u16, 1024>();
111
112        let len = unsafe {
113            FormatMessageW(
114                FORMAT_MESSAGE_FROM_SYSTEM
115                    | FORMAT_MESSAGE_IGNORE_INSERTS
116                    | FORMAT_MESSAGE_MAX_WIDTH_MASK,
117                ptr::null(),
118                self.0,
119                0,
120                buf[0].as_mut_ptr(),
121                buf.len() as _,
122                ptr::null_mut(),
123            )
124        } as usize;
125
126        if len == 0 {
127            // `FormatMessageW` failed -> use raw error code instead
128            write!(f, "{:#08X}", self.0)
129        } else {
130            // `FormatMessageW` succeeded -> convert to UTF8 and process
131            let wide_chars = unsafe { maybe_uninit_slice_assume_init_ref(&buf[..len]) };
132            let mut char_buf = maybe_uninit_uninit_array::<char, 1024>();
133
134            let char_iter = char::decode_utf16(wide_chars.iter().copied())
135                .map(|r| r.unwrap_or(char::REPLACEMENT_CHARACTER));
136
137            let mut i = 0;
138            for c in char_iter {
139                char_buf[i].write(c);
140                i += 1;
141            }
142
143            let chars = unsafe { maybe_uninit_slice_assume_init_ref(&char_buf[..i]) };
144            let start = chars.iter().position(|c| !c.is_whitespace()).unwrap_or(0);
145            let end = chars
146                .iter()
147                .rposition(|c| !c.is_whitespace())
148                .unwrap_or(chars.len());
149            for c in &chars[start..end] {
150                f.write_char(*c)?;
151            }
152
153            Ok(())
154        }
155    }
156}
157
158#[cfg(feature = "std")]
159impl Error for Win32Error {}
160
161#[cfg(feature = "std")]
162/// Error when converting a [`io::Error`] to a [`Win32Error`] when no raw os error code is available.
163#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
164pub struct TryFromIoError;
165
166#[cfg(feature = "std")]
167impl Display for TryFromIoError {
168    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169        write!(
170            f,
171            "the given io error did not contain a windows api error code."
172        )
173    }
174}
175
176#[cfg(feature = "std")]
177impl Error for TryFromIoError {}
178
179#[cfg(feature = "std")]
180impl TryFrom<io::Error> for Win32Error {
181    type Error = TryFromIoError;
182
183    fn try_from(err: io::Error) -> Result<Self, Self::Error> {
184        err.raw_os_error()
185            .map_or_else(|| Err(TryFromIoError), |code| Ok(Self::new(code as _)))
186    }
187}
188
189#[cfg(feature = "std")]
190impl From<Win32Error> for io::Error {
191    fn from(err: Win32Error) -> Self {
192        Self::from_raw_os_error(err.code() as _)
193    }
194}
195
196// TODO: replace with MaybeUninit::slice_assume_init_ref(slice) once stable
197const unsafe fn maybe_uninit_slice_assume_init_ref<T>(slice: &[MaybeUninit<T>]) -> &[T] {
198    unsafe { &*(slice as *const [MaybeUninit<T>] as *const [T]) }
199}
200
201// TODO: replace with MaybeUninit::uninit_array::<LEN, T>() once stable
202fn maybe_uninit_uninit_array<T, const LEN: usize>() -> [MaybeUninit<T>; LEN] {
203    unsafe { MaybeUninit::<[MaybeUninit<T>; LEN]>::uninit().assume_init() }
204}