windows_namespace_request_sys/outcome.rs
1// Copyright (c) Mike Grier.
2
3//! The faithful-execution contract every entry follows.
4//!
5//! An entry reports what Windows reported. It does not normalise a code, map it
6//! onto a friendlier taxonomy, or decide that one failure "really means"
7//! something else.
8//!
9//! # Why preservation is a constraint, not a preference
10//!
11//! `ERROR_FILE_NOT_FOUND` means three different things depending on which call
12//! produced it and when: a missing directory from an open, an **empty**
13//! directory from a first query, and a genuine failure from a later one. Only a
14//! consumer holding that context can tell them apart. Any reclassification here
15//! destroys information no layer above can reconstruct.
16//!
17//! # Why the code is snapshotted rather than read later
18//!
19//! `GetLastError` is thread state, and it is *volatile* thread state: almost
20//! any subsequent Win32 call overwrites it, including cleanup a caller does not
21//! think of as a call at all -- a `CloseHandle` in a `Drop`, a buffer being
22//! released, a restoration guard unwinding. Reading it a few statements after
23//! the failure is a race against the entry's own tidying up.
24//!
25//! So the read is not left to the caller's discipline. [`perform`] and its
26//! convention-specific forms take the call as a closure and snapshot the code
27//! **in the statement after it returns**, before anything else can run. Binding
28//! to these functions is what makes the guarantee structural rather than a rule
29//! each entry has to remember.
30//!
31//! # Scope: entries, not capture
32//!
33//! This governs the Win32 call an entry *performs*. It does not govern capture
34//! failures -- [`crate::handle`], [`crate::security`], and [`crate::path`]
35//! report a named stage plus a code, because there the useful question is which
36//! part of building the request went wrong. Those happen on the calling thread,
37//! before any entry runs.
38
39use std::fmt;
40use std::io;
41
42use windows_sys::Win32::Foundation::{
43 ERROR_NOT_ENOUGH_MEMORY, FALSE, GetLastError, HANDLE, INVALID_HANDLE_VALUE, WIN32_ERROR,
44};
45
46/// A raw Win32 error code, exactly as Windows produced it.
47///
48/// Deliberately not an enum: the point of this type is that it carries whatever
49/// Windows said, including codes this crate has never heard of.
50///
51/// # Example
52///
53/// ```
54/// use windows_namespace_request_sys::Win32Error;
55/// use windows_sys::Win32::Foundation::ERROR_FILE_NOT_FOUND;
56///
57/// let error = Win32Error::from_code(ERROR_FILE_NOT_FOUND);
58/// assert_eq!(error.code(), ERROR_FILE_NOT_FOUND);
59///
60/// // The io::Error form is a re-presentation, not a reclassification: the raw
61/// // code survives it.
62/// assert_eq!(error.to_io_error().raw_os_error(), Some(ERROR_FILE_NOT_FOUND as i32));
63///
64/// // A code this crate has never heard of is carried just the same.
65/// let unknown = Win32Error::from_code(0x0BAD_F00D);
66/// assert_eq!(unknown.code(), 0x0BAD_F00D);
67/// ```
68#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
69pub struct Win32Error(WIN32_ERROR);
70
71impl Win32Error {
72 /// Wraps a raw `WIN32_ERROR`.
73 #[must_use]
74 pub fn from_code(code: WIN32_ERROR) -> Self {
75 Self(code)
76 }
77
78 /// Snapshots the calling thread's last error.
79 ///
80 /// Call this only immediately after the failing Win32 call. Prefer
81 /// [`perform`], which makes that ordering structural.
82 fn last() -> Self {
83 // SAFETY: GetLastError only reads the calling thread's own error slot.
84 Self(unsafe { GetLastError() })
85 }
86
87 /// This crate's own report that it could not allocate memory for a call, which was then
88 /// not made: `ERROR_NOT_ENOUGH_MEMORY`. It is the one code here that the call did not
89 /// produce.
90 pub(crate) fn out_of_memory() -> Self {
91 Self(ERROR_NOT_ENOUGH_MEMORY)
92 }
93
94 /// The raw code, unaltered.
95 #[must_use]
96 pub fn code(self) -> WIN32_ERROR {
97 self.0
98 }
99
100 /// The same failure as a standard [`io::Error`], for callers that funnel
101 /// everything through `std::io`.
102 ///
103 /// This is a re-presentation, not a reclassification: the raw code survives
104 /// as [`io::Error::raw_os_error`].
105 #[must_use]
106 pub fn to_io_error(self) -> io::Error {
107 i32::try_from(self.0).map_or_else(
108 |_| io::Error::other(format!("Win32 error {}", self.0)),
109 io::Error::from_raw_os_error,
110 )
111 }
112}
113
114impl fmt::Display for Win32Error {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 write!(f, "Win32 error {}: {}", self.0, self.to_io_error())
117 }
118}
119
120impl std::error::Error for Win32Error {}
121
122/// What an entry's Win32 call produced: its result, or the raw code.
123pub type Outcome<T> = Result<T, Win32Error>;
124
125/// Performs `call` and, when `failed` says the result is a failure, snapshots
126/// the thread's last error before anything else can run.
127///
128/// `failed` decides using only the returned value, because Win32's failure
129/// conventions differ per call and none of them is inferable from the type. The
130/// three the catalogue actually meets have named forms:
131/// [`perform_bool`], [`perform_handle`], and [`perform_nonzero`]. Use this
132/// general form for a call whose convention is none of those.
133///
134/// # Example
135///
136/// The guarantee this function exists for. Cleanup between the failing call and
137/// the read is exactly what destroys a last-error code, and binding to
138/// `perform` closes that window:
139///
140/// ```
141/// use windows_namespace_request_sys::outcome::perform;
142/// use windows_sys::Win32::Foundation::{
143/// ERROR_ACCESS_DENIED, ERROR_FILE_NOT_FOUND, SetLastError,
144/// };
145///
146/// let outcome = perform(
147/// || {
148/// // SAFETY: SetLastError writes only this thread's error slot.
149/// unsafe { SetLastError(ERROR_FILE_NOT_FOUND) };
150/// -1_i32
151/// },
152/// |result| *result < 0,
153/// );
154///
155/// // Cleanup runs afterwards and clobbers the thread's error slot -- a Drop, a
156/// // buffer release, a restoration guard. The snapshot is already taken.
157/// // SAFETY: as above.
158/// unsafe { SetLastError(ERROR_ACCESS_DENIED) };
159///
160/// assert_eq!(
161/// outcome.expect_err("a negative result is a failure").code(),
162/// ERROR_FILE_NOT_FOUND
163/// );
164/// ```
165pub fn perform<T>(call: impl FnOnce() -> T, failed: impl FnOnce(&T) -> bool) -> Outcome<T> {
166 let result = call();
167
168 // Nothing may go between these two statements. No drop runs here, no
169 // cleanup, no second Win32 call -- which is the entire reason this function
170 // exists rather than each entry reading GetLastError for itself.
171 if failed(&result) {
172 return Err(Win32Error::last());
173 }
174
175 Ok(result)
176}
177
178/// Performs a call whose `BOOL` return is `FALSE` on failure.
179///
180/// The successful value carries no information beyond "it worked", so it is
181/// discarded rather than handed back as a bare integer.
182///
183/// # Errors
184///
185/// Returns the raw Win32 code when the call returns `FALSE`.
186pub fn perform_bool(call: impl FnOnce() -> i32) -> Outcome<()> {
187 perform(call, |result| *result == FALSE).map(|_| ())
188}
189
190/// Performs a call whose `HANDLE` return is `INVALID_HANDLE_VALUE` on failure.
191///
192/// This is the convention of `CreateFileW`, `OpenFileById`, and
193/// `FindFirstChangeNotificationW`.
194///
195/// # Errors
196///
197/// Returns the raw Win32 code when the call returns `INVALID_HANDLE_VALUE`.
198///
199/// # Example
200///
201/// The two handle conventions disagree about the same values, which is why both
202/// exist by name. Using one where the other belongs turns a failure into a
203/// plausible-looking handle:
204///
205/// ```
206/// use std::ptr;
207///
208/// use windows_namespace_request_sys::outcome::{perform_handle, perform_nonnull_handle};
209/// use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;
210///
211/// // Under the INVALID_HANDLE_VALUE convention, null is a *success*.
212/// assert!(perform_handle(ptr::null_mut).is_ok());
213/// assert!(perform_handle(|| INVALID_HANDLE_VALUE).is_err());
214///
215/// // Under the null convention, the two swap.
216/// assert!(perform_nonnull_handle(|| INVALID_HANDLE_VALUE).is_ok());
217/// assert!(perform_nonnull_handle(ptr::null_mut).is_err());
218/// ```
219pub fn perform_handle(call: impl FnOnce() -> HANDLE) -> Outcome<HANDLE> {
220 perform(call, |result| *result == INVALID_HANDLE_VALUE)
221}
222
223/// Performs a call whose `HANDLE` return is **null** on failure.
224///
225/// A distinct convention from [`perform_handle`], and getting the two the wrong
226/// way round turns a failure into a plausible-looking handle. Both are provided
227/// because Windows uses both.
228///
229/// # Errors
230///
231/// Returns the raw Win32 code when the call returns a null handle.
232pub fn perform_nonnull_handle(call: impl FnOnce() -> HANDLE) -> Outcome<HANDLE> {
233 perform(call, |result| result.is_null())
234}
235
236/// Performs a call whose numeric return is `0` on failure.
237///
238/// This is the convention of the sizing and length calls, such as
239/// `GetFullPathNameW` and `GetFinalPathNameByHandleW`.
240///
241/// # Errors
242///
243/// Returns the raw Win32 code when the call returns `0`.
244pub fn perform_nonzero(call: impl FnOnce() -> u32) -> Outcome<u32> {
245 perform(call, |result| *result == 0)
246}
247
248#[cfg(test)]
249mod tests;