Skip to main content

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;