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;