windows_thread_ambient_sys/declared.rs
1// Copyright (c) Mike Grier.
2
3//! The declared aspects: WOW64 filesystem redirection, memory priority, and I/O
4//! priority.
5//!
6//! These are *declared*, not captured. A declared aspect has nothing to collect
7//! from the calling thread, so it is not part of any capture set: the caller
8//! states the value it wants installed, and leaving it unspecified means the
9//! running thread's own value is untouched.
10//!
11//! # Why each one is declared, and the reasons differ
12//!
13//! Stating this per aspect rather than as one blanket claim matters, because the
14//! reasons are not the same and only one of them is absolute.
15//!
16//! - **WOW64 filesystem redirection has no getter at all.**
17//! `Wow64DisableWow64FsRedirection` yields an `OldValue` only as a side effect
18//! of *disabling* redirection, and there is no way to observe the current
19//! state without changing it. An aspect that cannot be read cannot be
20//! transplanted; this is a mechanical impossibility, not a preference.
21//! - **Memory priority is readable**, through
22//! `GetThreadInformation(ThreadMemoryPriority)`, so this one is a choice.
23//! Priority is a policy about how work should compete for resources, not
24//! something a caller implicitly consents to having remoted -- and silently
25//! transplanting it is not the safe default either way, since a caller in a
26//! background mode would have its remoted work quietly promoted or demoted
27//! without saying so.
28//! - **I/O priority has no documented getter**, and does not move on its own: it
29//! changes only in lockstep with CPU and memory priority, through background
30//! mode. So there is no independent value to capture even in principle, and
31//! declaring background mode declares all three together, which the type says
32//! out loud rather than hiding.
33//!
34//! # Redirection only does anything in a 32-bit process
35//!
36//! `Wow64DisableWow64FsRedirection` is meaningful only for a 32-bit process on
37//! 64-bit Windows. In a 64-bit process there is no redirector to disable and the
38//! call fails. That failure is reported rather than swallowed, because a caller
39//! that asked for redirection to be disabled and silently did not get it would
40//! be reading a different filesystem than it believes.
41//!
42//! # Example
43//!
44//! ```
45//! use windows_thread_ambient_sys::Declared;
46//! use windows_thread_ambient_sys::declared::MemoryPriority;
47//!
48//! let entry = MemoryPriority::current()?;
49//!
50//! // Only what is named is installed; every other aspect is left alone.
51//! let declared = Declared::none().with_memory_priority(MemoryPriority::Low);
52//!
53//! let during = declared.with_applied(MemoryPriority::current)?;
54//! assert_eq!(during?, MemoryPriority::Low);
55//!
56//! // The thread is returned to whatever it had, not to an assumed default.
57//! assert_eq!(MemoryPriority::current()?, entry);
58//! # Ok::<(), Box<dyn std::error::Error>>(())
59//! ```
60
61use std::fmt;
62use std::io;
63
64use windows_sys::Win32::Storage::FileSystem::{
65 Wow64DisableWow64FsRedirection, Wow64RevertWow64FsRedirection,
66};
67use windows_sys::Win32::System::Threading::{
68 GetCurrentThread, GetThreadInformation, MEMORY_PRIORITY, MEMORY_PRIORITY_BELOW_NORMAL,
69 MEMORY_PRIORITY_INFORMATION, MEMORY_PRIORITY_LOW, MEMORY_PRIORITY_MEDIUM,
70 MEMORY_PRIORITY_NORMAL, MEMORY_PRIORITY_VERY_LOW, SetThreadInformation, SetThreadPriority,
71 THREAD_MODE_BACKGROUND_BEGIN, THREAD_MODE_BACKGROUND_END, ThreadMemoryPriority,
72};
73
74/// How a thread's memory pages compete for physical memory.
75#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
76#[non_exhaustive]
77pub enum MemoryPriority {
78 /// The lowest priority; pages are trimmed first.
79 VeryLow,
80 /// Below `BelowNormal`.
81 Low,
82 /// Between `Low` and `BelowNormal`.
83 Medium,
84 /// Below the default.
85 BelowNormal,
86 /// The default for a thread.
87 Normal,
88}
89
90impl MemoryPriority {
91 /// The Win32 value.
92 #[must_use]
93 pub const fn as_raw(self) -> MEMORY_PRIORITY {
94 match self {
95 Self::VeryLow => MEMORY_PRIORITY_VERY_LOW,
96 Self::Low => MEMORY_PRIORITY_LOW,
97 Self::Medium => MEMORY_PRIORITY_MEDIUM,
98 Self::BelowNormal => MEMORY_PRIORITY_BELOW_NORMAL,
99 Self::Normal => MEMORY_PRIORITY_NORMAL,
100 }
101 }
102
103 /// Interpret a Win32 value.
104 #[must_use]
105 pub const fn from_raw(raw: MEMORY_PRIORITY) -> Option<Self> {
106 match raw {
107 MEMORY_PRIORITY_VERY_LOW => Some(Self::VeryLow),
108 MEMORY_PRIORITY_LOW => Some(Self::Low),
109 MEMORY_PRIORITY_MEDIUM => Some(Self::Medium),
110 MEMORY_PRIORITY_BELOW_NORMAL => Some(Self::BelowNormal),
111 MEMORY_PRIORITY_NORMAL => Some(Self::Normal),
112 _ => None,
113 }
114 }
115
116 /// Read the calling thread's current memory priority.
117 ///
118 /// Provided because the value *is* readable, which is what makes this
119 /// aspect's declared status a deliberate choice rather than a limitation. A
120 /// consumer that decides transplanting is right for it can read the value
121 /// here and declare it; the crate simply does not do so on the caller's
122 /// behalf.
123 ///
124 /// # Errors
125 ///
126 /// Returns [`DeclaredError`] if the query fails or reports a value this
127 /// enumeration does not name.
128 pub fn current() -> Result<Self, DeclaredError> {
129 let mut info = MEMORY_PRIORITY_INFORMATION {
130 MemoryPriority: MEMORY_PRIORITY_NORMAL,
131 };
132 // SAFETY: the destination is a valid, correctly sized struct and the
133 // pseudo-handle needs no cleanup.
134 let ok = unsafe {
135 GetThreadInformation(
136 GetCurrentThread(),
137 ThreadMemoryPriority,
138 std::ptr::from_mut(&mut info).cast(),
139 size_of::<MEMORY_PRIORITY_INFORMATION>() as u32,
140 )
141 };
142 if ok == 0 {
143 return Err(DeclaredError::new(DeclaredAspect::MemoryPriority));
144 }
145 Self::from_raw(info.MemoryPriority)
146 .ok_or_else(|| DeclaredError::without_os_error(DeclaredAspect::MemoryPriority))
147 }
148
149 fn install(self) -> Result<(), DeclaredError> {
150 #[cfg(test)]
151 if let Some(source) =
152 crate::test_injection::hit(crate::test_injection::FaultPoint::MemoryInstall)
153 {
154 return Err(DeclaredError {
155 aspect: DeclaredAspect::MemoryPriority,
156 source: Some(source),
157 });
158 }
159
160 let info = MEMORY_PRIORITY_INFORMATION {
161 MemoryPriority: self.as_raw(),
162 };
163 // SAFETY: the source is a valid, correctly sized struct and the
164 // pseudo-handle needs no cleanup.
165 let ok = unsafe {
166 SetThreadInformation(
167 GetCurrentThread(),
168 ThreadMemoryPriority,
169 std::ptr::from_ref(&info).cast(),
170 size_of::<MEMORY_PRIORITY_INFORMATION>() as u32,
171 )
172 };
173 if ok == 0 {
174 return Err(DeclaredError::new(DeclaredAspect::MemoryPriority));
175 }
176 Ok(())
177 }
178}
179
180/// Whether the thread runs in background processing mode.
181///
182/// Background mode is not an I/O-priority knob on its own: entering it lowers
183/// CPU, I/O **and** memory priority together, and leaving it restores all three.
184/// The name says so rather than presenting it as an I/O setting that happens to
185/// have side effects.
186#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
187pub enum BackgroundMode {
188 /// Enter background processing mode for the operation.
189 Begin,
190 /// Leave background processing mode for the operation.
191 End,
192}
193
194impl BackgroundMode {
195 fn install(self) -> Result<(), DeclaredError> {
196 #[cfg(test)]
197 if let Some(source) =
198 crate::test_injection::hit(crate::test_injection::FaultPoint::BackgroundInstall)
199 {
200 return Err(DeclaredError {
201 aspect: DeclaredAspect::BackgroundMode,
202 source: Some(source),
203 });
204 }
205
206 let value = match self {
207 Self::Begin => THREAD_MODE_BACKGROUND_BEGIN,
208 Self::End => THREAD_MODE_BACKGROUND_END,
209 };
210 // SAFETY: the pseudo-handle needs no cleanup and `value` is a documented
211 // background-mode selector.
212 let ok = unsafe { SetThreadPriority(GetCurrentThread(), value) };
213 if ok == 0 {
214 return Err(DeclaredError::new(DeclaredAspect::BackgroundMode));
215 }
216 Ok(())
217 }
218
219 /// The call that undoes this one.
220 const fn inverse(self) -> Self {
221 match self {
222 Self::Begin => Self::End,
223 Self::End => Self::Begin,
224 }
225 }
226}
227
228/// What to do with WOW64 filesystem redirection.
229///
230/// Only [`Disabled`](Self::Disabled) is expressible, because Windows offers no
231/// way to *enable* redirection that was never disabled, and no way to read the
232/// current state. The enum exists rather than a bare `bool` so that a later
233/// capability does not change the meaning of an existing value.
234#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
235#[non_exhaustive]
236pub enum Wow64Redirection {
237 /// Disable redirection for the operation, restoring it afterwards.
238 Disabled,
239}
240
241/// Which declared aspect an operation concerned.
242#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
243#[non_exhaustive]
244pub enum DeclaredAspect {
245 /// WOW64 filesystem redirection.
246 Wow64Redirection,
247 /// Thread memory priority.
248 MemoryPriority,
249 /// Background processing mode.
250 BackgroundMode,
251}
252
253/// A declared aspect could not be installed or restored.
254#[derive(Debug)]
255pub struct DeclaredError {
256 aspect: DeclaredAspect,
257 source: Option<io::Error>,
258}
259
260impl DeclaredError {
261 #[cfg(test)]
262 pub(crate) fn for_test(aspect: DeclaredAspect, code: Option<i32>) -> Self {
263 Self {
264 aspect,
265 source: code.map(io::Error::from_raw_os_error),
266 }
267 }
268
269 fn new(aspect: DeclaredAspect) -> Self {
270 Self {
271 aspect,
272 source: Some(io::Error::last_os_error()),
273 }
274 }
275
276 const fn without_os_error(aspect: DeclaredAspect) -> Self {
277 Self {
278 aspect,
279 source: None,
280 }
281 }
282
283 /// Builds a `DeclaredError` reporting a specific outcome, for tests
284 /// elsewhere in the crate that need one without provoking a real Win32
285 /// failure -- redirection's is only reachable in a 32-bit process, and
286 /// memory priority and background mode have no known failure mode at all
287 /// on a real thread.
288 #[cfg(test)]
289 pub(crate) fn synthetic(aspect: DeclaredAspect, os_error: Option<i32>) -> Self {
290 Self {
291 aspect,
292 source: os_error.map(io::Error::from_raw_os_error),
293 }
294 }
295
296 /// Which aspect failed.
297 #[must_use]
298 pub const fn aspect(&self) -> DeclaredAspect {
299 self.aspect
300 }
301
302 /// The underlying Win32 code, if there was one.
303 #[must_use]
304 pub fn raw_os_error(&self) -> Option<i32> {
305 self.source.as_ref().and_then(io::Error::raw_os_error)
306 }
307}
308
309fn revert_redirection(old: *mut core::ffi::c_void) -> Result<(), DeclaredError> {
310 #[cfg(test)]
311 let injected = crate::test_injection::hit(crate::test_injection::FaultPoint::RedirectionRevert);
312
313 // SAFETY: `old` is the value produced by the matching disable call.
314 let ok = {
315 #[cfg(test)]
316 {
317 if injected.is_some() {
318 0
319 } else {
320 // SAFETY: `old` is the value produced by the matching disable call.
321 unsafe { Wow64RevertWow64FsRedirection(old) }
322 }
323 }
324 #[cfg(not(test))]
325 {
326 // SAFETY: `old` is the value produced by the matching disable call.
327 unsafe { Wow64RevertWow64FsRedirection(old) }
328 }
329 };
330 if ok == 0 {
331 #[cfg(test)]
332 if let Some(source) = injected {
333 return Err(DeclaredError {
334 aspect: DeclaredAspect::Wow64Redirection,
335 source: Some(source),
336 });
337 }
338 Err(DeclaredError::new(DeclaredAspect::Wow64Redirection))
339 } else {
340 Ok(())
341 }
342}
343
344impl fmt::Display for DeclaredError {
345 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
346 let what = match self.aspect {
347 DeclaredAspect::Wow64Redirection => {
348 "WOW64 filesystem redirection could not be changed (it exists only \
349 for a 32-bit process on 64-bit Windows)"
350 }
351 DeclaredAspect::MemoryPriority => "the thread memory priority could not be read or set",
352 DeclaredAspect::BackgroundMode => "background processing mode could not be changed",
353 };
354 match &self.source {
355 Some(source) => write!(f, "{what}: {source}"),
356 None => f.write_str(what),
357 }
358 }
359}
360
361impl std::error::Error for DeclaredError {
362 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
363 self.source
364 .as_ref()
365 .map(|source| source as &(dyn std::error::Error + 'static))
366 }
367}
368
369/// The declared aspects a caller wants installed.
370///
371/// Every field is optional, and `None` means **leave the running thread's own
372/// value alone**. That is not the same as declaring a default: a declared
373/// default would overwrite whatever the thread had.
374#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
375pub struct Declared {
376 /// WOW64 filesystem redirection.
377 pub wow64_redirection: Option<Wow64Redirection>,
378 /// Thread memory priority.
379 pub memory_priority: Option<MemoryPriority>,
380 /// Background processing mode, which moves CPU, I/O and memory priority
381 /// together.
382 pub background_mode: Option<BackgroundMode>,
383}
384
385impl Declared {
386 /// Declare nothing, leaving every aspect of the running thread alone.
387 #[must_use]
388 pub const fn none() -> Self {
389 Self {
390 wow64_redirection: None,
391 memory_priority: None,
392 background_mode: None,
393 }
394 }
395
396 /// Whether anything at all would be installed.
397 #[must_use]
398 pub const fn is_empty(&self) -> bool {
399 self.wow64_redirection.is_none()
400 && self.memory_priority.is_none()
401 && self.background_mode.is_none()
402 }
403
404 /// Declare WOW64 filesystem redirection.
405 #[must_use]
406 pub const fn with_wow64_redirection(mut self, value: Wow64Redirection) -> Self {
407 self.wow64_redirection = Some(value);
408 self
409 }
410
411 /// Declare a memory priority.
412 #[must_use]
413 pub const fn with_memory_priority(mut self, value: MemoryPriority) -> Self {
414 self.memory_priority = Some(value);
415 self
416 }
417
418 /// Declare a background processing mode.
419 #[must_use]
420 pub const fn with_background_mode(mut self, value: BackgroundMode) -> Self {
421 self.background_mode = Some(value);
422 self
423 }
424
425 /// Install these aspects on the calling thread until the guard is released.
426 ///
427 /// Aspects are installed in a fixed order -- background mode, then memory
428 /// priority, then WOW64 redirection -- and
429 /// [`release`](DeclaredGuard::release) undoes them in exact reverse, so the
430 /// thread passes back through each intermediate state rather than being
431 /// snapped to an assumed one.
432 ///
433 /// # Errors
434 ///
435 /// Returns [`DeclaredError`] if an aspect could not be installed. Every
436 /// aspect installed before the failing one is released first, so a failed
437 /// install leaves the thread as it found it.
438 pub fn install(&self) -> Result<DeclaredGuard, DeclaredError> {
439 let background = match self.background_mode {
440 Some(mode) => {
441 mode.install()?;
442 Some(mode)
443 }
444 None => None,
445 };
446
447 let memory = match self.memory_priority {
448 Some(priority) => {
449 let previous = MemoryPriority::current();
450 match previous.and_then(|previous| priority.install().map(|()| previous)) {
451 Ok(previous) => Some(previous),
452 Err(error) => {
453 release_background(background);
454 return Err(error);
455 }
456 }
457 }
458 None => None,
459 };
460
461 let redirection = match self.wow64_redirection {
462 Some(Wow64Redirection::Disabled) => {
463 let mut old: *mut core::ffi::c_void = std::ptr::null_mut();
464 // SAFETY: `old` is a valid writable destination, and is passed
465 // back verbatim to the revert call in `release`.
466 let ok = unsafe { Wow64DisableWow64FsRedirection(&mut old) };
467 if ok == 0 {
468 let error = DeclaredError::new(DeclaredAspect::Wow64Redirection);
469 release_memory(memory);
470 release_background(background);
471 return Err(error);
472 }
473 Some(old)
474 }
475 None => None,
476 };
477
478 Ok(DeclaredGuard {
479 background,
480 memory,
481 redirection,
482 released: false,
483 })
484 }
485
486 /// Run `operation` with these aspects installed.
487 ///
488 /// A convenience over [`install`](Self::install) for callers that do not
489 /// need the operation's value when restoration fails. **A restore failure
490 /// discards the value**, because there is no room in this signature for
491 /// both; a caller that must keep the value even then should use `install`
492 /// and [`DeclaredGuard::release`] directly, as the composite does.
493 ///
494 /// # Errors
495 ///
496 /// Returns [`DeclaredError`] if an aspect could not be installed, in which
497 /// case `operation` did not run; or if an aspect could not be restored
498 /// afterwards, in which case it did run and its value is discarded.
499 pub fn with_applied<F, T>(&self, operation: F) -> Result<T, DeclaredError>
500 where
501 F: FnOnce() -> T,
502 {
503 let guard = self.install()?;
504 let outcome = operation();
505 guard.release().map(|()| outcome)
506 }
507}
508
509/// Holds declared aspects installed until released.
510///
511/// Not `Send`: it restores the thread it was created on, and the WOW64 revert
512/// token is meaningless anywhere else. Moving one to another thread would
513/// revert *that* thread's redirection using a cookie minted for a different
514/// one.
515///
516/// As with [`TransactionGuard`], that property is a consequence of a field
517/// type rather than an explicit bound -- `redirection` is a raw pointer, and
518/// raw pointers are not `Send`. Replacing it with an integer newtype would
519/// silently make this guard `Send` and this paragraph false, so the claim is
520/// pinned by a test rather than left as prose:
521///
522/// ```compile_fail,E0277
523/// fn assert_send<T: Send>() {}
524/// assert_send::<windows_thread_ambient_sys::declared::DeclaredGuard>();
525/// ```
526///
527/// [`TransactionGuard`]: crate::transaction::TransactionGuard
528#[must_use = "dropping the guard restores the aspects but discards any failure to do so"]
529#[derive(Debug)]
530pub struct DeclaredGuard {
531 background: Option<BackgroundMode>,
532 memory: Option<MemoryPriority>,
533 redirection: Option<*mut core::ffi::c_void>,
534 released: bool,
535}
536
537impl DeclaredGuard {
538 /// Restore every installed aspect, in exact reverse order.
539 ///
540 /// # Errors
541 ///
542 /// Returns the **first** [`DeclaredError`] encountered, having still
543 /// attempted every remaining restoration -- stopping early would leave more
544 /// of the thread contaminated than necessary.
545 pub fn release(mut self) -> Result<(), DeclaredError> {
546 self.released = true;
547 Self::restore(self.background, self.memory, self.redirection)
548 }
549
550 fn restore(
551 background: Option<BackgroundMode>,
552 memory: Option<MemoryPriority>,
553 redirection: Option<*mut core::ffi::c_void>,
554 ) -> Result<(), DeclaredError> {
555 let mut failure = None;
556 if let Some(old) = redirection
557 && let Err(error) = revert_redirection(old)
558 {
559 failure = Some(error);
560 }
561 if let Some(previous) = memory
562 && let Err(error) = previous.install()
563 {
564 failure = failure.or(Some(error));
565 }
566 if let Some(mode) = background
567 && let Err(error) = mode.inverse().install()
568 {
569 failure = failure.or(Some(error));
570 }
571 match failure {
572 Some(error) => Err(error),
573 None => Ok(()),
574 }
575 }
576}
577
578impl Drop for DeclaredGuard {
579 fn drop(&mut self) {
580 if !self.released {
581 // Best effort: a destructor has no caller to report to.
582 let _ = Self::restore(self.background, self.memory, self.redirection);
583 }
584 }
585}
586
587fn release_background(background: Option<BackgroundMode>) {
588 if let Some(mode) = background {
589 let _ = mode.inverse().install();
590 }
591}
592
593fn release_memory(memory: Option<MemoryPriority>) {
594 if let Some(previous) = memory {
595 let _ = previous.install();
596 }
597}
598
599#[cfg(test)]
600mod tests;