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