windows_thread_ambient_sys/error_mode.rs
1// Copyright (c) Mike Grier.
2
3//! The thread error mode aspect.
4//!
5//! The thread error mode decides whether a hard device error -- the classic
6//! absent-removable-drive case -- raises a modal dialog or fails the call. A
7//! thread-pool worker's mode is `0`, meaning the critical-error handler is
8//! enabled, so a blocking call remoted onto shared infrastructure can put a
9//! dialog on a thread the whole process depends on.
10//!
11//! # This aspect is both capturable and declarable, deliberately
12//!
13//! Unlike the others it appears in both halves of the crate's decomposition. It
14//! is readable, so a caller may capture the submitting thread's value and
15//! transplant it; and it is the aspect consumers most often want to *override*
16//! with a policy of their own. Offering only one of those would bake one
17//! consumer's answer into a platform layer: a consumer running on shared threads
18//! will force the dialog-suppressing bits, while a consumer owning a private
19//! thread, where a modal dialog is its own problem and nobody else's, is
20//! entitled to the opposite choice.
21//!
22//! # Why the alignment bit is not representable
23//!
24//! [`ThreadErrorMode`] can hold only the three bits `SetThreadErrorMode`
25//! accepts. `SEM_NOALIGNMENTFAULTEXCEPT` is excluded because it is *rejected*
26//! per-thread -- and, measured, an invalid bit fails the **whole** call rather
27//! than being dropped from it. A type that could represent it would let a caller
28//! combine it with valid bits and silently lose the entire change, so this is a
29//! case for a type that cannot express the invalid state rather than a runtime
30//! check nobody expected to fail. See
31//! [`windows-platform-probes`](../../windows-platform-probes/DESIGN-NOTES.md),
32//! which pins the measurement as a test.
33//!
34//! # Example
35//!
36//! ```
37//! use windows_thread_ambient_sys::ThreadErrorMode;
38//!
39//! let entry = ThreadErrorMode::capture()?;
40//!
41//! let mode = ThreadErrorMode::FAIL_CRITICAL_ERRORS
42//! .union(ThreadErrorMode::NO_OPEN_FILE_ERROR_BOX);
43//! let guard = mode.apply()?;
44//! assert!(ThreadErrorMode::capture()?.contains(ThreadErrorMode::FAIL_CRITICAL_ERRORS));
45//!
46//! // Release explicitly. Dropping the guard also restores, but discards any
47//! // failure to do so, because a destructor has no caller to report to.
48//! guard.release()?;
49//! assert_eq!(ThreadErrorMode::capture()?, entry);
50//! # Ok::<(), Box<dyn std::error::Error>>(())
51//! ```
52
53use std::fmt;
54use std::io;
55
56use windows_sys::Win32::System::Diagnostics::Debug::{
57 GetThreadErrorMode, SEM_FAILCRITICALERRORS, SEM_NOGPFAULTERRORBOX, SEM_NOOPENFILEERRORBOX,
58 SetThreadErrorMode, THREAD_ERROR_MODE,
59};
60
61/// Every bit this crate will place in a [`ThreadErrorMode`].
62const SUPPORTED: THREAD_ERROR_MODE =
63 SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX;
64
65/// A thread error mode, restricted to the bits Windows accepts per thread.
66///
67/// Construct one from the associated constants and [`union`](Self::union), or
68/// from a raw value with [`from_bits`](Self::from_bits).
69#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
70pub struct ThreadErrorMode(THREAD_ERROR_MODE);
71
72impl ThreadErrorMode {
73 /// No bits set: hard errors raise the system's dialogs.
74 pub const NONE: Self = Self(0);
75
76 /// Fail calls that hit a critical error instead of raising a dialog.
77 pub const FAIL_CRITICAL_ERRORS: Self = Self(SEM_FAILCRITICALERRORS);
78
79 /// Suppress the general-protection-fault error box.
80 pub const NO_GP_FAULT_ERROR_BOX: Self = Self(SEM_NOGPFAULTERRORBOX);
81
82 /// Suppress the file-open error box.
83 pub const NO_OPEN_FILE_ERROR_BOX: Self = Self(SEM_NOOPENFILEERRORBOX);
84
85 /// The raw value, as Win32 represents it.
86 #[must_use]
87 pub const fn bits(self) -> THREAD_ERROR_MODE {
88 self.0
89 }
90
91 /// Build a mode from a raw value.
92 ///
93 /// # Errors
94 ///
95 /// Returns [`UnsupportedBits`] if `bits` contains anything
96 /// `SetThreadErrorMode` does not accept. This is a rejection rather than a
97 /// mask: silently dropping a bit would report installing a value that was
98 /// not installed, which is the failure this type exists to prevent.
99 ///
100 /// # Example
101 ///
102 /// ```
103 /// use windows_thread_ambient_sys::ThreadErrorMode;
104 ///
105 /// assert_eq!(
106 /// ThreadErrorMode::from_bits(0x0001),
107 /// Ok(ThreadErrorMode::FAIL_CRITICAL_ERRORS)
108 /// );
109 ///
110 /// // 0x0004 is SEM_NOALIGNMENTFAULTEXCEPT, which cannot be set per thread.
111 /// // It is refused even beside a valid bit, because Windows would install
112 /// // neither: an invalid bit fails the whole call.
113 /// let refused = ThreadErrorMode::from_bits(0x0001 | 0x0004)
114 /// .expect_err("the alignment bit is not settable per thread");
115 /// assert_eq!(refused.bits(), 0x0004);
116 /// ```
117 pub const fn from_bits(bits: THREAD_ERROR_MODE) -> Result<Self, UnsupportedBits> {
118 let unsupported = bits & !SUPPORTED;
119 if unsupported == 0 {
120 Ok(Self(bits))
121 } else {
122 Err(UnsupportedBits { bits: unsupported })
123 }
124 }
125
126 /// Both modes' bits.
127 #[must_use]
128 pub const fn union(self, other: Self) -> Self {
129 Self(self.0 | other.0)
130 }
131
132 /// Whether every bit of `other` is set.
133 #[must_use]
134 pub const fn contains(self, other: Self) -> bool {
135 self.0 & other.0 == other.0
136 }
137
138 /// Whether no bits are set.
139 #[must_use]
140 pub const fn is_empty(self) -> bool {
141 self.0 == 0
142 }
143
144 /// Read the calling thread's current mode.
145 ///
146 /// # Errors
147 ///
148 /// Returns [`UnsupportedBits`] if Windows reports a bit this type cannot
149 /// hold. Measured, that does not happen: the thread error mode is
150 /// independent storage rather than a view of the process error mode, so a
151 /// process-scope bit such as `SEM_NOALIGNMENTFAULTEXCEPT` does not show
152 /// through here. The result is still surfaced rather than assumed away,
153 /// because a type unable to represent a state the platform can produce would
154 /// be a bug, and this is where that would first be observable.
155 pub fn capture() -> Result<Self, UnsupportedBits> {
156 // SAFETY: the call takes no arguments and has no preconditions.
157 Self::from_bits(unsafe { GetThreadErrorMode() })
158 }
159
160 /// Install this mode on the calling thread until the guard is released.
161 ///
162 /// # Errors
163 ///
164 /// Returns [`ApplyError`] if Windows refuses the value, in which case
165 /// nothing was installed and the thread is untouched.
166 pub fn apply(self) -> Result<ErrorModeGuard, ApplyError> {
167 let mut previous: THREAD_ERROR_MODE = 0;
168 // SAFETY: `previous` is a valid writable destination, and `self.0`
169 // cannot contain a bit the call rejects.
170 let ok = unsafe { SetThreadErrorMode(self.0, &mut previous) };
171 if ok == 0 {
172 return Err(ApplyError {
173 requested: self,
174 source: io::Error::last_os_error(),
175 });
176 }
177 Ok(ErrorModeGuard {
178 previous,
179 released: false,
180 })
181 }
182}
183
184impl fmt::Display for ThreadErrorMode {
185 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186 write!(f, "0x{:04X}", self.0)
187 }
188}
189
190/// A raw value contained bits the per-thread error mode does not accept.
191#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
192pub struct UnsupportedBits {
193 bits: THREAD_ERROR_MODE,
194}
195
196impl UnsupportedBits {
197 /// Just the offending bits.
198 #[must_use]
199 pub const fn bits(self) -> THREAD_ERROR_MODE {
200 self.bits
201 }
202}
203
204impl fmt::Display for UnsupportedBits {
205 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206 write!(
207 f,
208 "0x{:04X} cannot be set on a thread; SetThreadErrorMode rejects it \
209 and would install none of the accompanying bits either",
210 self.bits
211 )
212 }
213}
214
215impl std::error::Error for UnsupportedBits {}
216
217/// Windows refused to install a thread error mode.
218#[derive(Debug)]
219pub struct ApplyError {
220 requested: ThreadErrorMode,
221 source: io::Error,
222}
223
224impl ApplyError {
225 /// The mode that could not be installed.
226 #[must_use]
227 pub const fn requested(&self) -> ThreadErrorMode {
228 self.requested
229 }
230
231 /// The underlying Win32 code, if there was one.
232 #[must_use]
233 pub fn raw_os_error(&self) -> Option<i32> {
234 self.source.raw_os_error()
235 }
236}
237
238impl fmt::Display for ApplyError {
239 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240 write!(
241 f,
242 "could not install thread error mode {}: {}",
243 self.requested, self.source
244 )
245 }
246}
247
248impl std::error::Error for ApplyError {
249 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
250 Some(&self.source)
251 }
252}
253
254/// Windows refused to restore the thread's entry error mode.
255#[derive(Debug)]
256pub struct RestoreError {
257 unrestored: THREAD_ERROR_MODE,
258 source: io::Error,
259}
260
261impl RestoreError {
262 /// The value the thread should have been returned to.
263 #[must_use]
264 pub const fn unrestored_bits(&self) -> THREAD_ERROR_MODE {
265 self.unrestored
266 }
267
268 /// The underlying Win32 code, if there was one.
269 #[must_use]
270 pub fn raw_os_error(&self) -> Option<i32> {
271 self.source.raw_os_error()
272 }
273}
274
275impl fmt::Display for RestoreError {
276 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277 write!(
278 f,
279 "could not restore thread error mode 0x{:04X}; the thread is left \
280 contaminated: {}",
281 self.unrestored, self.source
282 )
283 }
284}
285
286impl std::error::Error for RestoreError {
287 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
288 Some(&self.source)
289 }
290}
291
292/// Holds an installed error mode until it is released.
293///
294/// # Release explicitly on the ordinary path
295///
296/// [`release`](Self::release) reports whether the thread was actually restored.
297/// Dropping the guard instead restores on a best-effort basis and **discards**
298/// any failure, because a destructor has no caller to report to. That is the
299/// right behaviour while unwinding, where no report could be delivered anyway,
300/// and the wrong behaviour on the ordinary path -- so the ordinary path calls
301/// `release`.
302///
303/// Restoration failure is not fatal here. Contrast impersonation, whose restore
304/// failure is fail-fast because returning a shared worker under an unknown
305/// identity is a process-wide security failure; leaving a thread with the wrong
306/// error mode is a real contamination but not that, and imposing the strictest
307/// aspect's semantics on every aspect is precisely what this crate's composite
308/// exists to avoid.
309#[must_use = "dropping the guard restores the error mode but discards any failure to do so"]
310#[derive(Debug)]
311pub struct ErrorModeGuard {
312 previous: THREAD_ERROR_MODE,
313 released: bool,
314}
315
316impl ErrorModeGuard {
317 /// The mode this thread had before the guard was installed.
318 ///
319 /// # Errors
320 ///
321 /// Returns [`UnsupportedBits`] in the same unreachable-by-measurement case
322 /// as [`ThreadErrorMode::capture`]. The guard itself keeps the raw value, so
323 /// restoration round-trips exactly whatever Windows reported, whether or not
324 /// this crate's type can name it.
325 pub const fn previous(&self) -> Result<ThreadErrorMode, UnsupportedBits> {
326 ThreadErrorMode::from_bits(self.previous)
327 }
328
329 /// Restore the thread's entry mode, reporting whether it worked.
330 ///
331 /// # Errors
332 ///
333 /// Returns [`RestoreError`] if Windows refused, leaving the thread
334 /// contaminated with whatever was installed.
335 pub fn release(mut self) -> Result<(), RestoreError> {
336 self.released = true;
337 Self::restore(self.previous)
338 }
339
340 fn restore(previous: THREAD_ERROR_MODE) -> Result<(), RestoreError> {
341 let mut ignored: THREAD_ERROR_MODE = 0;
342 // SAFETY: `ignored` is a valid writable destination, and `previous` is a
343 // value Windows itself reported for this thread.
344 let ok = unsafe { SetThreadErrorMode(previous, &mut ignored) };
345 if ok == 0 {
346 return Err(RestoreError {
347 unrestored: previous,
348 source: io::Error::last_os_error(),
349 });
350 }
351 Ok(())
352 }
353}
354
355impl Drop for ErrorModeGuard {
356 fn drop(&mut self) {
357 if !self.released {
358 // Best effort by design: see the type's documentation.
359 let _ = Self::restore(self.previous);
360 }
361 }
362}
363
364#[cfg(test)]
365mod tests;