Skip to main content

windows_thread_ambient_sys/
state.rs

1// Copyright (c) Mike Grier.
2
3//! The composite: a thread's ambient state, captured as one value.
4//!
5//! [`AmbientState`] holds every aspect together, so a caller carries one value
6//! to a worker rather than remembering which pieces it collected. Its field list
7//! is contract surface: it is exhaustively enumerated, and a silently added
8//! field would be a silent semantic change.
9//!
10//! # Capture fails on the calling thread, never later
11//!
12//! Capture is synchronous and happens where the caller can still act on the
13//! result. A context that cannot be captured is an **admission** failure, not a
14//! deferred one -- a worker discovering it later has no way to report it to
15//! anyone who can do anything about it, and by then the caller has usually moved
16//! on. The error names the aspect that failed, because "capture failed" is not
17//! actionable when three aspects could have caused it.
18//!
19//! # Declared aspects are not captured
20//!
21//! [`Declared`] values are supplied by the caller and read from nothing, so they
22//! are attached with [`AmbientState::with_declared`] rather than collected. That
23//! separation is why the capture set names only capturable aspects.
24//!
25//! # One capture can serve many workers at once
26//!
27//! [`AmbientState`] is both `Send` and `Sync`, so a single capture may be shared
28//! through an `Arc` and applied concurrently on any number of workers. That is
29//! the shape a traversal or scan engine actually has: capture once at
30//! submission, then run it on every worker for the length of the job. Each
31//! application installs and restores on its own thread and observes nothing of
32//! the others.
33//!
34//! Sharing is also the cheap option. Capture duplicates a kernel token object,
35//! so re-capturing per unit of work re-pays for a snapshot the caller already
36//! holds.
37//!
38//! # Granularity is the caller's choice, and it costs something
39//!
40//! Applying once around a batch of operations and applying once per operation
41//! are both expressible, and the crate deliberately does not choose. Each
42//! application is a `SetThreadToken` plus a call for every other aspect in play,
43//! so a worker that opens a thousand files pays that a thousand times if it
44//! applies per open.
45//!
46//! Prefer the widest window the aspects allow -- but note that the *narrowest*
47//! window is sometimes the correct one for a reason unrelated to cost:
48//! [crates/windows-file-enumeration-sys](../../windows-file-enumeration-sys/DESIGN-NOTES.md)
49//! deliberately impersonates only around its directory open, because every later
50//! query uses the resulting handle and needs no token at all. Holding a token
51//! longer than the work requires is a security decision, not just a performance
52//! one.
53//!
54//! # The blast radius of fail-fast restoration
55//!
56//! A failure to restore impersonation panics. **That is this crate's decision,
57//! not one inherited from a dependency**: a shared worker returned to a pool
58//! under an unknown identity is a process-wide security failure, and no error
59//! return could make a caller notice in time.
60//! [`windows_impersonation_token_sys`] is used because its behaviour already
61//! satisfies that requirement; if it stopped doing so, the dependency would be
62//! wrong and this guarantee would stay.
63//!
64//! The consequence is worth stating plainly for anyone running many impersonated
65//! workers. A panic inside a thread-pool callback **aborts the process** -- the
66//! pool has no caller to unwind to -- so a restore failure on one worker of
67//! sixty-four is not one failed operation, it is the whole process. This is the
68//! intended trade, and a consumer that cannot accept it should not be applying
69//! impersonation on threads it does not own.
70//!
71//! # Example
72//!
73//! ```
74//! use windows_thread_ambient_sys::declared::MemoryPriority;
75//! use windows_thread_ambient_sys::{AmbientState, CaptureSet, Declared};
76//!
77//! // Collected from this thread, right now, where a failure is still ours.
78//! let state = AmbientState::capture(CaptureSet::DEFAULT)?
79//!     // Stated rather than read: nothing was collected for this.
80//!     .with_declared(Declared::none().with_memory_priority(MemoryPriority::Low));
81//!
82//! // What was asked for is recoverable afterwards, which is what keeps an
83//! // omission distinguishable from an aspect that was captured and empty.
84//! assert_eq!(state.captured_set(), CaptureSet::DEFAULT);
85//! assert!(state.impersonation().was_captured());
86//! assert!(!state.transaction().was_captured());
87//!
88//! // Applying installs every aspect in a fixed order and releases in exact
89//! // reverse. An uncaptured aspect is skipped, leaving the running thread's own
90//! // value alone.
91//! let applied = state.with_applied(|| "work")?;
92//! assert_eq!(*applied.value(), "work");
93//! assert!(applied.restore().is_clean());
94//! # Ok::<(), Box<dyn std::error::Error>>(())
95//! ```
96
97use std::fmt;
98
99use windows_impersonation_token_sys::{
100    ApplyError as ImpersonationApplyError, CaptureError as ImpersonationCaptureError,
101    ImpersonationToken,
102};
103
104use crate::capture_set::{CapturableAspect, CaptureSet};
105use crate::captured::Captured;
106use crate::declared::{Declared, DeclaredError};
107use crate::error_mode::{
108    ApplyError as ErrorModeApplyError, RestoreError as ErrorModeRestoreError, ThreadErrorMode,
109    UnsupportedBits,
110};
111use crate::transaction::{TransactionContext, TransactionError};
112use crate::{impersonation, transaction};
113/// Which aspect failed to capture, and why.
114#[derive(Debug)]
115#[non_exhaustive]
116pub enum CaptureFailure {
117    /// The impersonation context could not be captured.
118    Impersonation(ImpersonationCaptureError),
119    /// The thread error mode reported a value this crate cannot represent.
120    ErrorMode(UnsupportedBits),
121    /// The current transaction could not be captured.
122    Transaction(TransactionError),
123}
124
125/// A composite capture failed.
126///
127/// The failing aspect is **derived** from the failure rather than stored beside
128/// it, so the two cannot disagree.
129#[derive(Debug)]
130pub struct CaptureError {
131    failure: CaptureFailure,
132}
133
134impl CaptureError {
135    /// Which aspect failed.
136    #[must_use]
137    pub const fn aspect(&self) -> CapturableAspect {
138        match self.failure {
139            CaptureFailure::Impersonation(_) => CapturableAspect::Impersonation,
140            CaptureFailure::ErrorMode(_) => CapturableAspect::ErrorMode,
141            CaptureFailure::Transaction(_) => CapturableAspect::Transaction,
142        }
143    }
144
145    /// The underlying failure.
146    #[must_use]
147    pub const fn failure(&self) -> &CaptureFailure {
148        &self.failure
149    }
150
151    /// The underlying Win32 code, if the failing aspect reported one.
152    #[must_use]
153    pub fn raw_os_error(&self) -> Option<i32> {
154        match &self.failure {
155            CaptureFailure::Impersonation(error) => error.raw_os_error(),
156            CaptureFailure::ErrorMode(_) => None,
157            CaptureFailure::Transaction(error) => error.raw_os_error(),
158        }
159    }
160}
161
162impl fmt::Display for CaptureError {
163    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164        write!(f, "capturing the {} aspect failed: ", self.aspect())?;
165        match &self.failure {
166            CaptureFailure::Impersonation(error) => write!(f, "{error}"),
167            CaptureFailure::ErrorMode(error) => write!(f, "{error}"),
168            CaptureFailure::Transaction(error) => write!(f, "{error}"),
169        }
170    }
171}
172
173impl std::error::Error for CaptureError {
174    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
175        match &self.failure {
176            CaptureFailure::Impersonation(error) => Some(error),
177            CaptureFailure::ErrorMode(error) => Some(error),
178            CaptureFailure::Transaction(error) => Some(error),
179        }
180    }
181}
182
183/// A thread's ambient state, captured and declared, ready to travel.
184///
185/// The field list is exhaustive on purpose; see the module documentation.
186#[derive(Debug)]
187#[must_use = "an ambient state that is never applied captured a context for nothing"]
188pub struct AmbientState {
189    impersonation: Captured<ImpersonationToken>,
190    error_mode: Captured<ThreadErrorMode>,
191    transaction: Captured<TransactionContext>,
192    declared: Declared,
193}
194
195impl AmbientState {
196    /// Capture the aspects `set` names from the calling thread.
197    ///
198    /// Aspects outside `set` are [`Captured::NotCaptured`], which leaves the
199    /// target thread's own value alone when the state is later applied -- a
200    /// different thing from an aspect that was captured and found empty.
201    ///
202    /// Declared aspects are not touched here; attach them with
203    /// [`with_declared`](Self::with_declared).
204    ///
205    /// # Errors
206    ///
207    /// Returns [`CaptureError`], naming the aspect that failed. Any aspect
208    /// captured before the failure is released rather than leaked, so a failed
209    /// capture holds nothing.
210    pub fn capture(set: CaptureSet) -> Result<Self, CaptureError> {
211        // Order follows `CapturableAspect::EVERY` so the sequence is the one the
212        // set reports, rather than an incidental one.
213        let impersonation = if set.contains(CaptureSet::IMPERSONATION) {
214            impersonation::capture().map_err(|error| CaptureError {
215                failure: CaptureFailure::Impersonation(error),
216            })?
217        } else {
218            Captured::NotCaptured
219        };
220
221        let error_mode = if set.contains(CaptureSet::ERROR_MODE) {
222            Captured::Present(ThreadErrorMode::capture().map_err(|error| CaptureError {
223                failure: CaptureFailure::ErrorMode(error),
224            })?)
225        } else {
226            Captured::NotCaptured
227        };
228
229        let transaction = if set.contains(CaptureSet::TRANSACTION) {
230            transaction::capture().map_err(|error| CaptureError {
231                failure: CaptureFailure::Transaction(error),
232            })?
233        } else {
234            Captured::NotCaptured
235        };
236
237        Ok(Self {
238            impersonation,
239            error_mode,
240            transaction,
241            declared: Declared::none(),
242        })
243    }
244
245    /// Attach declared aspects, replacing any already attached.
246    pub fn with_declared(mut self, declared: Declared) -> Self {
247        self.declared = declared;
248        self
249    }
250
251    /// What was actually collected.
252    ///
253    /// **Derived** from the aspects themselves rather than recorded separately,
254    /// so it cannot disagree with what the state holds.
255    #[must_use]
256    pub fn captured_set(&self) -> CaptureSet {
257        let mut set = CaptureSet::NONE;
258        if self.impersonation.was_captured() {
259            set = set.union(CaptureSet::IMPERSONATION);
260        }
261        if self.error_mode.was_captured() {
262            set = set.union(CaptureSet::ERROR_MODE);
263        }
264        if self.transaction.was_captured() {
265            set = set.union(CaptureSet::TRANSACTION);
266        }
267        set
268    }
269
270    /// The captured impersonation context.
271    #[must_use]
272    pub const fn impersonation(&self) -> &Captured<ImpersonationToken> {
273        &self.impersonation
274    }
275
276    /// The captured thread error mode.
277    #[must_use]
278    pub const fn error_mode(&self) -> &Captured<ThreadErrorMode> {
279        &self.error_mode
280    }
281
282    /// The captured transaction.
283    #[must_use]
284    pub const fn transaction(&self) -> &Captured<TransactionContext> {
285        &self.transaction
286    }
287
288    /// The declared aspects.
289    #[must_use]
290    pub const fn declared(&self) -> &Declared {
291        &self.declared
292    }
293
294    /// Run `operation` with this state installed on the calling thread.
295    ///
296    /// # Order
297    ///
298    /// Guards are applied outermost-first and released in **exact reverse**, so
299    /// the thread passes back through each intermediate state:
300    ///
301    /// 1. thread error mode -- outermost, so hard-error suppression is already
302    ///    in force while everything else is being applied;
303    /// 2. declared aspects (background mode, memory priority, redirection);
304    /// 3. TxF transaction;
305    /// 4. impersonation -- innermost, because its window is the narrowest and
306    ///    its restoration is the one that must not be delayed.
307    ///
308    /// Applying a subset stays expressible: an aspect that is
309    /// [`Captured::NotCaptured`] or unspecified is skipped entirely, leaving the
310    /// running thread's own value alone.
311    ///
312    /// # Overriding rather than transplanting the error mode
313    ///
314    /// This applies the error mode it *captured*. A consumer that wants to
315    /// impose its own -- forcing the dialog-suppressing bits on a shared worker,
316    /// say -- should leave [`CaptureSet::ERROR_MODE`] out of its capture set and
317    /// wrap this call in its own [`ThreadErrorMode::apply`] guard, which then
318    /// sits outermost, exactly where the order above puts it. Capturing *and*
319    /// overriding would install the captured value inside the override.
320    ///
321    /// # Errors
322    ///
323    /// Returns [`ApplyError`] if an aspect could not be installed, in which case
324    /// `operation` did not run and every already-installed aspect is released
325    /// first.
326    ///
327    /// A failure to **restore** is different, and does not fail the call: the
328    /// operation ran and its value is kept, with the failures reported through
329    /// [`Applied::restore`]. Discarding a successful operation's value because a
330    /// priority could not be put back would lose more than it protects.
331    ///
332    /// # Panics
333    ///
334    /// **Panics if the impersonation context cannot be restored, and that is
335    /// this crate's guarantee rather than a detail of its dependencies.**
336    /// Returning a shared worker to a pool under an unknown identity is a
337    /// process-wide security failure: every later task on that thread would run
338    /// as whoever the failed restore left behind, and no caller could detect it
339    /// from a returned error. Failing fast is the only response that cannot be
340    /// ignored, which is a different order of hazard from the other aspects
341    /// here, and they are reported rather than fatal.
342    ///
343    /// [`windows_impersonation_token_sys`] is used because its behaviour
344    /// already satisfies that guarantee. If it ever stopped doing so, the
345    /// dependency would be wrong and this contract would not change -- an
346    /// earlier version of this note described the semantics as *inherited* from
347    /// that crate and "not chosen here", which left a security property of this
348    /// public API resting on someone else's implementation detail.
349    pub fn with_applied<F, T>(&self, operation: F) -> Result<Applied<T>, ApplyError>
350    where
351        F: FnOnce() -> T,
352    {
353        // 1. Error mode, outermost.
354        let error_mode_guard = match self.error_mode.present() {
355            Some(mode) => Some(mode.apply().map_err(|error| ApplyError {
356                failure: ApplyFailure::ErrorMode(error),
357            })?),
358            None => None,
359        };
360
361        // 2. Declared aspects.
362        let declared_guard = match self.declared.install() {
363            Ok(guard) => guard,
364            Err(error) => {
365                drop(error_mode_guard);
366                return Err(ApplyError {
367                    failure: ApplyFailure::Declared(error),
368                });
369            }
370        };
371
372        // 3. Transaction.
373        let transaction_guard = match transaction::install(&self.transaction) {
374            Ok(guard) => guard,
375            Err(error) => {
376                drop(declared_guard);
377                drop(error_mode_guard);
378                return Err(ApplyError {
379                    failure: ApplyFailure::Transaction(error),
380                });
381            }
382        };
383
384        // 4. Impersonation, innermost, and closure-scoped by its own crate.
385        let outcome = impersonation::with_applied(&self.impersonation, operation);
386        let value = match outcome {
387            Ok(value) => value,
388            Err(error) => {
389                drop(transaction_guard);
390                drop(declared_guard);
391                drop(error_mode_guard);
392                return Err(ApplyError {
393                    failure: ApplyFailure::Impersonation(error),
394                });
395            }
396        };
397
398        // Release in exact reverse. Every release is attempted even after one
399        // fails, because stopping early leaves more of the thread contaminated.
400        //
401        // These are separate statements rather than a struct literal on purpose:
402        // the order below *is* the release order, and burying it in field
403        // initialisers would make a later reader's harmless-looking field
404        // reordering silently reorder the releases.
405        let transaction = transaction_guard.release().err();
406        let declared = declared_guard.release().err();
407        let error_mode = match error_mode_guard {
408            Some(guard) => guard.release().err(),
409            None => None,
410        };
411
412        Ok(Applied {
413            value,
414            restore: RestoreReport {
415                error_mode,
416                declared,
417                transaction,
418            },
419        })
420    }
421}
422
423/// What an operation produced, and whether the thread was put back.
424#[derive(Debug)]
425#[must_use = "ignoring the restore report discards evidence that the thread is contaminated"]
426pub struct Applied<T> {
427    value: T,
428    restore: RestoreReport,
429}
430
431impl<T> Applied<T> {
432    /// The operation's value.
433    pub const fn value(&self) -> &T {
434        &self.value
435    }
436
437    /// Take the value, deliberately ignoring the restore report.
438    pub fn into_value(self) -> T {
439        self.value
440    }
441
442    /// Which aspects failed to restore, if any.
443    pub const fn restore(&self) -> &RestoreReport {
444        &self.restore
445    }
446
447    /// Take the value only if the thread was restored cleanly.
448    ///
449    /// # Errors
450    ///
451    /// Returns the report when any aspect failed to restore. The value is
452    /// dropped in that case, so a caller that needs both should use
453    /// [`value`](Self::value) and [`restore`](Self::restore) instead.
454    pub fn into_clean_value(self) -> Result<T, RestoreReport> {
455        if self.restore.is_clean() {
456            Ok(self.value)
457        } else {
458            Err(self.restore)
459        }
460    }
461}
462
463/// Which aspects could not be restored after an operation.
464///
465/// Exhaustively enumerated rather than a list, so a reader can see every aspect
466/// that can appear without running anything. Impersonation is absent by
467/// construction: its restore failure is fatal, so it never reaches a report.
468#[derive(Debug, Default)]
469pub struct RestoreReport {
470    error_mode: Option<ErrorModeRestoreError>,
471    declared: Option<DeclaredError>,
472    transaction: Option<TransactionError>,
473}
474
475impl RestoreReport {
476    /// Whether every aspect was restored.
477    #[must_use]
478    pub const fn is_clean(&self) -> bool {
479        self.error_mode.is_none() && self.declared.is_none() && self.transaction.is_none()
480    }
481
482    /// The thread error mode's restore failure, if any.
483    #[must_use]
484    pub const fn error_mode(&self) -> Option<&ErrorModeRestoreError> {
485        self.error_mode.as_ref()
486    }
487
488    /// The declared aspects' restore failure, if any.
489    #[must_use]
490    pub const fn declared(&self) -> Option<&DeclaredError> {
491        self.declared.as_ref()
492    }
493
494    /// The transaction's restore failure, if any.
495    #[must_use]
496    pub const fn transaction(&self) -> Option<&TransactionError> {
497        self.transaction.as_ref()
498    }
499}
500
501impl fmt::Display for RestoreReport {
502    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
503        if self.is_clean() {
504            return f.write_str("the thread was restored cleanly");
505        }
506        f.write_str("the thread is contaminated:")?;
507        if let Some(error) = &self.error_mode {
508            write!(f, " error mode: {error};")?;
509        }
510        if let Some(error) = &self.declared {
511            write!(f, " declared: {error};")?;
512        }
513        if let Some(error) = &self.transaction {
514            write!(f, " transaction: {error};")?;
515        }
516        Ok(())
517    }
518}
519
520impl std::error::Error for RestoreReport {}
521
522/// Which aspect could not be installed, and why.
523#[derive(Debug)]
524#[non_exhaustive]
525pub enum ApplyFailure {
526    /// The thread error mode could not be installed.
527    ErrorMode(ErrorModeApplyError),
528    /// A declared aspect could not be installed.
529    Declared(DeclaredError),
530    /// The transaction could not be installed.
531    Transaction(TransactionError),
532    /// The impersonation context could not be applied.
533    Impersonation(ImpersonationApplyError),
534}
535
536/// Applying a composite state failed, so the operation did not run.
537#[derive(Debug)]
538pub struct ApplyError {
539    failure: ApplyFailure,
540}
541
542impl ApplyError {
543    /// The underlying failure, whose variant names the aspect.
544    #[must_use]
545    pub const fn failure(&self) -> &ApplyFailure {
546        &self.failure
547    }
548
549    /// The underlying Win32 code, if the failing aspect reported one.
550    #[must_use]
551    pub fn raw_os_error(&self) -> Option<i32> {
552        match &self.failure {
553            ApplyFailure::ErrorMode(error) => error.raw_os_error(),
554            ApplyFailure::Declared(error) => error.raw_os_error(),
555            ApplyFailure::Transaction(error) => error.raw_os_error(),
556            ApplyFailure::Impersonation(error) => error.raw_os_error(),
557        }
558    }
559}
560
561impl fmt::Display for ApplyError {
562    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
563        f.write_str("applying the ambient state failed: ")?;
564        match &self.failure {
565            ApplyFailure::ErrorMode(error) => write!(f, "{error}"),
566            ApplyFailure::Declared(error) => write!(f, "{error}"),
567            ApplyFailure::Transaction(error) => write!(f, "{error}"),
568            ApplyFailure::Impersonation(error) => write!(f, "{error}"),
569        }
570    }
571}
572
573impl std::error::Error for ApplyError {
574    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
575        match &self.failure {
576            ApplyFailure::ErrorMode(error) => Some(error),
577            ApplyFailure::Declared(error) => Some(error),
578            ApplyFailure::Transaction(error) => Some(error),
579            ApplyFailure::Impersonation(error) => Some(error),
580        }
581    }
582}
583
584#[cfg(test)]
585mod tests;