windows_thread_ambient_sys/lib.rs
1// Copyright (c) Mike Grier.
2
3//! Capture a Windows thread's ambient state and apply it on another thread.
4//!
5//! Some Windows behaviour is not a parameter of the call you make; it is
6//! ambient state hanging off the calling thread. An impersonation token decides
7//! whose access rights an open is checked against, and even which drive letters
8//! resolve. The thread error mode decides whether a hard device error raises a
9//! modal dialog. WOW64 filesystem redirection decides which of two directories a
10//! 32-bit process actually reaches. None of it travels with work handed to
11//! another thread.
12//!
13//! That matters most when the other thread is shared. A thread-pool worker
14//! inherits none of the submitter's ambient state: measured, `OpenThreadToken`
15//! on a worker returns `ERROR_NO_TOKEN` while the submitting thread genuinely
16//! held a token, and the worker's error mode is `0`, meaning the critical-error
17//! handler is enabled and an absent removable drive can put a modal dialog on
18//! process-shared infrastructure. Explicit capture is therefore necessary rather
19//! than merely prudent.
20//!
21//! # Scope
22//!
23//! This crate carries thread-scoped ambient state that changes what a Win32 call
24//! does. It does not carry call parameters, does not open files, and does not
25//! know what any particular Windows operation is.
26//!
27//! # Two sets, because the aspects do not relate to the caller the same way
28//!
29//! Aspects that can be read off the calling thread are **captured**, and which
30//! of them to collect is chosen by the caller. Aspects that cannot be read --
31//! WOW64 redirection has no getter at all, and I/O priority has no documented
32//! one -- are **declared** instead: the caller states the value it wants
33//! installed. A declared aspect has nothing to collect, so it is not part of the
34//! capture set; left unspecified, it leaves the target thread's own value alone.
35//!
36//! # This crate holds no policy
37//!
38//! Every aspect is offered for capture *and* for explicit declaration, and no
39//! combination is privileged. A consumer running on shared threads will want to
40//! force the dialog-suppressing error-mode bits; a consumer with a private
41//! thread, where a modal dialog is its own problem and nobody else's, is
42//! entitled to the opposite choice. Both compose that policy from the primitives
43//! here rather than finding it already decided.
44//!
45//! # Example
46//!
47//! Capture on the submitting thread, where a failure is still the caller's to
48//! see, then reconstruct the context on a worker that inherited none of it:
49//!
50//! ```
51//! use std::thread;
52//!
53//! use windows_thread_ambient_sys::declared::MemoryPriority;
54//! use windows_thread_ambient_sys::{AmbientState, CaptureSet, Declared};
55//!
56//! // Captured here, on the submitting thread, where a failure is still ours to
57//! // see. Declared aspects are stated rather than read from anything.
58//! let state = AmbientState::capture(CaptureSet::DEFAULT)?
59//! .with_declared(Declared::none().with_memory_priority(MemoryPriority::Low));
60//!
61//! let applied = thread::spawn(move || {
62//! // Guards apply outermost-first and release in exact reverse, with
63//! // impersonation innermost because its window is the narrowest.
64//! state.with_applied(|| "ran as the submitter")
65//! })
66//! .join()
67//! .expect("the worker did not panic")?;
68//!
69//! assert_eq!(*applied.value(), "ran as the submitter");
70//!
71//! // A restore failure for the reported aspects -- the error mode, the
72//! // declared aspects, the transaction -- does not discard the operation's
73//! // value; it arrives alongside it, so a caller can retire a contaminated
74//! // thread without losing what the work produced.
75//! assert!(applied.restore().is_clean());
76//! # Ok::<(), Box<dyn std::error::Error>>(())
77//! ```
78//!
79//! Impersonation is deliberately *not* among those reported aspects: its restore
80//! is fail-fast, so a failure panics instead of being reported, and a panic
81//! inside a thread-pool callback aborts the process rather than failing one
82//! operation. That is the intended trade rather than an oversight, and it is the
83//! one property to weigh before adopting this crate on shared workers;
84//! [`state`] gives the reasoning in full.
85//!
86//! A consumer that wants to *override* the error mode rather than transplant it
87//! -- forcing the dialog-suppressing bits on a shared worker -- leaves
88//! [`CaptureSet::ERROR_MODE`] out of its capture set and wraps the call in its
89//! own [`ThreadErrorMode::apply`] guard, which then sits outermost, exactly
90//! where the ordering puts it.
91
92#![cfg(windows)]
93#![forbid(unsafe_op_in_unsafe_fn)]
94#![warn(missing_docs)]
95
96pub mod capture_set;
97pub mod captured;
98pub mod declared;
99pub mod error_mode;
100pub mod impersonation;
101pub mod state;
102pub mod transaction;
103
104pub use capture_set::{CapturableAspect, CaptureSet};
105pub use captured::Captured;
106pub use declared::Declared;
107pub use state::{
108 AmbientState, Applied, ApplyError, ApplyFailure, CaptureError, CaptureFailure, RestoreReport,
109};
110
111/// Compiles the README's examples, so a contract change breaks the build rather
112/// than silently teaching the old answer.
113#[cfg(doctest)]
114#[doc = include_str!("../README.md")]
115struct ReadmeDoctests;
116pub use error_mode::ThreadErrorMode;
117pub use windows_impersonation_token_sys::ImpersonationToken;