windows-thread-ambient-sys 0.2.0

Capture a Windows thread's ambient state and apply it on another thread.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
// Copyright (c) Mike Grier.

//! The composite: a thread's ambient state, captured as one value.
//!
//! [`AmbientState`] holds every aspect together, so a caller carries one value
//! to a worker rather than remembering which pieces it collected. Its field list
//! is contract surface: it is exhaustively enumerated, and a silently added
//! field would be a silent semantic change.
//!
//! # Capture fails on the calling thread, never later
//!
//! Capture is synchronous and happens where the caller can still act on the
//! result. A context that cannot be captured is an **admission** failure, not a
//! deferred one -- a worker discovering it later has no way to report it to
//! anyone who can do anything about it, and by then the caller has usually moved
//! on. The error names the aspect that failed, because "capture failed" is not
//! actionable when three aspects could have caused it.
//!
//! # Declared aspects are not captured
//!
//! [`Declared`] values are supplied by the caller and read from nothing, so they
//! are attached with [`AmbientState::with_declared`] rather than collected. That
//! separation is why the capture set names only capturable aspects.
//!
//! # One capture can serve many workers at once
//!
//! [`AmbientState`] is both `Send` and `Sync`, so a single capture may be shared
//! through an `Arc` and applied concurrently on any number of workers. That is
//! the shape a traversal or scan engine actually has: capture once at
//! submission, then run it on every worker for the length of the job. Each
//! application installs and restores on its own thread and observes nothing of
//! the others.
//!
//! Sharing is also the cheap option. Capture duplicates a kernel token object,
//! so re-capturing per unit of work re-pays for a snapshot the caller already
//! holds.
//!
//! # Granularity is the caller's choice, and it costs something
//!
//! Applying once around a batch of operations and applying once per operation
//! are both expressible, and the crate deliberately does not choose. Each
//! application is a `SetThreadToken` plus a call for every other aspect in play,
//! so a worker that opens a thousand files pays that a thousand times if it
//! applies per open.
//!
//! Prefer the widest window the aspects allow -- but note that the *narrowest*
//! window is sometimes the correct one for a reason unrelated to cost:
//! [crates/windows-file-enumeration-sys](../../windows-file-enumeration-sys/DESIGN-NOTES.md)
//! deliberately impersonates only around its directory open, because every later
//! query uses the resulting handle and needs no token at all. Holding a token
//! longer than the work requires is a security decision, not just a performance
//! one.
//!
//! # The blast radius of fail-fast restoration
//!
//! A failure to restore impersonation panics. That is inherited from
//! [`windows_impersonation_token_sys`] rather than chosen here, and it is
//! correct: a shared worker returned to a pool under an unknown identity is a
//! process-wide security failure.
//!
//! The consequence is worth stating plainly for anyone running many impersonated
//! workers. A panic inside a thread-pool callback **aborts the process** -- the
//! pool has no caller to unwind to -- so a restore failure on one worker of
//! sixty-four is not one failed operation, it is the whole process. This is the
//! intended trade, and a consumer that cannot accept it should not be applying
//! impersonation on threads it does not own.
//!
//! # Example
//!
//! ```
//! use windows_thread_ambient_sys::declared::MemoryPriority;
//! use windows_thread_ambient_sys::{AmbientState, CaptureSet, Declared};
//!
//! // Collected from this thread, right now, where a failure is still ours.
//! let state = AmbientState::capture(CaptureSet::DEFAULT)?
//!     // Stated rather than read: nothing was collected for this.
//!     .with_declared(Declared::none().with_memory_priority(MemoryPriority::Low));
//!
//! // What was asked for is recoverable afterwards, which is what keeps an
//! // omission distinguishable from an aspect that was captured and empty.
//! assert_eq!(state.captured_set(), CaptureSet::DEFAULT);
//! assert!(state.impersonation().was_captured());
//! assert!(!state.transaction().was_captured());
//!
//! // Applying installs every aspect in a fixed order and releases in exact
//! // reverse. An uncaptured aspect is skipped, leaving the running thread's own
//! // value alone.
//! let applied = state.with_applied(|| "work")?;
//! assert_eq!(*applied.value(), "work");
//! assert!(applied.restore().is_clean());
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```

use std::fmt;

use windows_impersonation_token_sys::{
    ApplyError as ImpersonationApplyError, CaptureError as ImpersonationCaptureError,
    ImpersonationToken,
};

use crate::capture_set::{CapturableAspect, CaptureSet};
use crate::captured::Captured;
use crate::declared::{Declared, DeclaredError};
use crate::error_mode::{
    ApplyError as ErrorModeApplyError, ErrorModeGuard, RestoreError as ErrorModeRestoreError,
    ThreadErrorMode, UnsupportedBits,
};
use crate::transaction::{TransactionContext, TransactionError};
use crate::{impersonation, transaction};
/// Which aspect failed to capture, and why.
#[derive(Debug)]
#[non_exhaustive]
pub enum CaptureFailure {
    /// The impersonation context could not be captured.
    Impersonation(ImpersonationCaptureError),
    /// The thread error mode reported a value this crate cannot represent.
    ErrorMode(UnsupportedBits),
    /// The current transaction could not be captured.
    Transaction(TransactionError),
}

/// A composite capture failed.
///
/// The failing aspect is **derived** from the failure rather than stored beside
/// it, so the two cannot disagree.
#[derive(Debug)]
pub struct CaptureError {
    failure: CaptureFailure,
}

impl CaptureError {
    /// Which aspect failed.
    #[must_use]
    pub const fn aspect(&self) -> CapturableAspect {
        match self.failure {
            CaptureFailure::Impersonation(_) => CapturableAspect::Impersonation,
            CaptureFailure::ErrorMode(_) => CapturableAspect::ErrorMode,
            CaptureFailure::Transaction(_) => CapturableAspect::Transaction,
        }
    }

    /// The underlying failure.
    #[must_use]
    pub const fn failure(&self) -> &CaptureFailure {
        &self.failure
    }

    /// The underlying Win32 code, if the failing aspect reported one.
    #[must_use]
    pub fn raw_os_error(&self) -> Option<i32> {
        match &self.failure {
            CaptureFailure::Impersonation(error) => error.raw_os_error(),
            CaptureFailure::ErrorMode(_) => None,
            CaptureFailure::Transaction(error) => error.raw_os_error(),
        }
    }
}

impl fmt::Display for CaptureError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "capturing the {} aspect failed: ", self.aspect())?;
        match &self.failure {
            CaptureFailure::Impersonation(error) => write!(f, "{error}"),
            CaptureFailure::ErrorMode(error) => write!(f, "{error}"),
            CaptureFailure::Transaction(error) => write!(f, "{error}"),
        }
    }
}

impl std::error::Error for CaptureError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match &self.failure {
            CaptureFailure::Impersonation(error) => Some(error),
            CaptureFailure::ErrorMode(error) => Some(error),
            CaptureFailure::Transaction(error) => Some(error),
        }
    }
}

/// A thread's ambient state, captured and declared, ready to travel.
///
/// The field list is exhaustive on purpose; see the module documentation.
#[derive(Debug)]
#[must_use = "an ambient state that is never applied captured a context for nothing"]
pub struct AmbientState {
    impersonation: Captured<ImpersonationToken>,
    error_mode: Captured<ThreadErrorMode>,
    transaction: Captured<TransactionContext>,
    declared: Declared,
}

impl AmbientState {
    /// Capture the aspects `set` names from the calling thread.
    ///
    /// Aspects outside `set` are [`Captured::NotCaptured`], which leaves the
    /// target thread's own value alone when the state is later applied -- a
    /// different thing from an aspect that was captured and found empty.
    ///
    /// Declared aspects are not touched here; attach them with
    /// [`with_declared`](Self::with_declared).
    ///
    /// # Errors
    ///
    /// Returns [`CaptureError`], naming the aspect that failed. Any aspect
    /// captured before the failure is released rather than leaked, so a failed
    /// capture holds nothing.
    pub fn capture(set: CaptureSet) -> Result<Self, CaptureError> {
        // Order follows `CapturableAspect::EVERY` so the sequence is the one the
        // set reports, rather than an incidental one.
        let impersonation = if set.contains(CaptureSet::IMPERSONATION) {
            impersonation::capture().map_err(|error| CaptureError {
                failure: CaptureFailure::Impersonation(error),
            })?
        } else {
            Captured::NotCaptured
        };

        let error_mode = if set.contains(CaptureSet::ERROR_MODE) {
            Captured::Present(ThreadErrorMode::capture().map_err(|error| CaptureError {
                failure: CaptureFailure::ErrorMode(error),
            })?)
        } else {
            Captured::NotCaptured
        };

        let transaction = if set.contains(CaptureSet::TRANSACTION) {
            transaction::capture().map_err(|error| CaptureError {
                failure: CaptureFailure::Transaction(error),
            })?
        } else {
            Captured::NotCaptured
        };

        Ok(Self {
            impersonation,
            error_mode,
            transaction,
            declared: Declared::none(),
        })
    }

    /// Attach declared aspects, replacing any already attached.
    pub fn with_declared(mut self, declared: Declared) -> Self {
        self.declared = declared;
        self
    }

    /// What was actually collected.
    ///
    /// **Derived** from the aspects themselves rather than recorded separately,
    /// so it cannot disagree with what the state holds.
    #[must_use]
    pub fn captured_set(&self) -> CaptureSet {
        let mut set = CaptureSet::NONE;
        if self.impersonation.was_captured() {
            set = set.union(CaptureSet::IMPERSONATION);
        }
        if self.error_mode.was_captured() {
            set = set.union(CaptureSet::ERROR_MODE);
        }
        if self.transaction.was_captured() {
            set = set.union(CaptureSet::TRANSACTION);
        }
        set
    }

    /// The captured impersonation context.
    #[must_use]
    pub const fn impersonation(&self) -> &Captured<ImpersonationToken> {
        &self.impersonation
    }

    /// The captured thread error mode.
    #[must_use]
    pub const fn error_mode(&self) -> &Captured<ThreadErrorMode> {
        &self.error_mode
    }

    /// The captured transaction.
    #[must_use]
    pub const fn transaction(&self) -> &Captured<TransactionContext> {
        &self.transaction
    }

    /// The declared aspects.
    #[must_use]
    pub const fn declared(&self) -> &Declared {
        &self.declared
    }

    /// Run `operation` with this state installed on the calling thread.
    ///
    /// # Order
    ///
    /// Guards are applied outermost-first and released in **exact reverse**, so
    /// the thread passes back through each intermediate state:
    ///
    /// 1. thread error mode -- outermost, so hard-error suppression is already
    ///    in force while everything else is being applied;
    /// 2. declared aspects (background mode, memory priority, redirection);
    /// 3. TxF transaction;
    /// 4. impersonation -- innermost, because its window is the narrowest and
    ///    its restoration is the one that must not be delayed.
    ///
    /// Applying a subset stays expressible: an aspect that is
    /// [`Captured::NotCaptured`] or unspecified is skipped entirely, leaving the
    /// running thread's own value alone.
    ///
    /// # Overriding rather than transplanting the error mode
    ///
    /// This applies the error mode it *captured*. A consumer that wants to
    /// impose its own -- forcing the dialog-suppressing bits on a shared worker,
    /// say -- should leave [`CaptureSet::ERROR_MODE`] out of its capture set and
    /// wrap this call in its own [`ThreadErrorMode::apply`] guard, which then
    /// sits outermost, exactly where the order above puts it. Capturing *and*
    /// overriding would install the captured value inside the override.
    ///
    /// # Errors
    ///
    /// Returns [`ApplyError`] if an aspect could not be installed, in which case
    /// `operation` did not run and every already-installed aspect is released
    /// first.
    ///
    /// A failure to **restore** is different, and does not fail the call: the
    /// operation ran and its value is kept, with the failures reported through
    /// [`Applied::restore`]. Discarding a successful operation's value because a
    /// priority could not be put back would lose more than it protects.
    ///
    /// # Panics
    ///
    /// Panics if the impersonation context cannot be restored. That semantics is
    /// inherited from
    /// [`windows_impersonation_token_sys`](windows_impersonation_token_sys),
    /// not chosen here: returning a shared worker to a pool under an unknown
    /// identity is a process-wide security failure, which is a different order
    /// of hazard from the other aspects.
    pub fn with_applied<F, T>(&self, operation: F) -> Result<Applied<T>, ApplyError>
    where
        F: FnOnce() -> T,
    {
        // 1. Error mode, outermost.
        let error_mode_guard = match self.error_mode.present() {
            Some(mode) => Some(mode.apply().map_err(|error| ApplyError {
                failure: ApplyFailure::ErrorMode(error),
            })?),
            None => None,
        };

        // 2. Declared aspects.
        let declared_guard = match self.declared.install() {
            Ok(guard) => guard,
            Err(error) => {
                release_error_mode(error_mode_guard);
                return Err(ApplyError {
                    failure: ApplyFailure::Declared(error),
                });
            }
        };

        // 3. Transaction.
        let transaction_guard = match transaction::install(&self.transaction) {
            Ok(guard) => guard,
            Err(error) => {
                drop(declared_guard);
                release_error_mode(error_mode_guard);
                return Err(ApplyError {
                    failure: ApplyFailure::Transaction(error),
                });
            }
        };

        // 4. Impersonation, innermost, and closure-scoped by its own crate.
        let outcome = impersonation::with_applied(&self.impersonation, operation);
        let value = match outcome {
            Ok(value) => value,
            Err(error) => {
                drop(transaction_guard);
                drop(declared_guard);
                release_error_mode(error_mode_guard);
                return Err(ApplyError {
                    failure: ApplyFailure::Impersonation(error),
                });
            }
        };

        // Release in exact reverse. Every release is attempted even after one
        // fails, because stopping early leaves more of the thread contaminated.
        //
        // These are separate statements rather than a struct literal on purpose:
        // the order below *is* the release order, and burying it in field
        // initialisers would make a later reader's harmless-looking field
        // reordering silently reorder the releases.
        let transaction = transaction_guard.release().err();
        let declared = declared_guard.release().err();
        let error_mode = match error_mode_guard {
            Some(guard) => guard.release().err(),
            None => None,
        };

        Ok(Applied {
            value,
            restore: RestoreReport {
                error_mode,
                declared,
                transaction,
            },
        })
    }
}

fn release_error_mode(guard: Option<ErrorModeGuard>) {
    if let Some(guard) = guard {
        // Best effort: an install failed, so this path already has an error to
        // report and a second one would displace it.
        let _ = guard.release();
    }
}

/// What an operation produced, and whether the thread was put back.
#[derive(Debug)]
#[must_use = "ignoring the restore report discards evidence that the thread is contaminated"]
pub struct Applied<T> {
    value: T,
    restore: RestoreReport,
}

impl<T> Applied<T> {
    /// The operation's value.
    pub const fn value(&self) -> &T {
        &self.value
    }

    /// Take the value, deliberately ignoring the restore report.
    pub fn into_value(self) -> T {
        self.value
    }

    /// Which aspects failed to restore, if any.
    pub const fn restore(&self) -> &RestoreReport {
        &self.restore
    }

    /// Take the value only if the thread was restored cleanly.
    ///
    /// # Errors
    ///
    /// Returns the report when any aspect failed to restore. The value is
    /// dropped in that case, so a caller that needs both should use
    /// [`value`](Self::value) and [`restore`](Self::restore) instead.
    pub fn into_clean_value(self) -> Result<T, RestoreReport> {
        if self.restore.is_clean() {
            Ok(self.value)
        } else {
            Err(self.restore)
        }
    }
}

/// Which aspects could not be restored after an operation.
///
/// Exhaustively enumerated rather than a list, so a reader can see every aspect
/// that can appear without running anything. Impersonation is absent by
/// construction: its restore failure is fatal, so it never reaches a report.
#[derive(Debug, Default)]
pub struct RestoreReport {
    error_mode: Option<ErrorModeRestoreError>,
    declared: Option<DeclaredError>,
    transaction: Option<TransactionError>,
}

impl RestoreReport {
    /// Whether every aspect was restored.
    #[must_use]
    pub const fn is_clean(&self) -> bool {
        self.error_mode.is_none() && self.declared.is_none() && self.transaction.is_none()
    }

    /// The thread error mode's restore failure, if any.
    #[must_use]
    pub const fn error_mode(&self) -> Option<&ErrorModeRestoreError> {
        self.error_mode.as_ref()
    }

    /// The declared aspects' restore failure, if any.
    #[must_use]
    pub const fn declared(&self) -> Option<&DeclaredError> {
        self.declared.as_ref()
    }

    /// The transaction's restore failure, if any.
    #[must_use]
    pub const fn transaction(&self) -> Option<&TransactionError> {
        self.transaction.as_ref()
    }
}

impl fmt::Display for RestoreReport {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if self.is_clean() {
            return f.write_str("the thread was restored cleanly");
        }
        f.write_str("the thread is contaminated:")?;
        if let Some(error) = &self.error_mode {
            write!(f, " error mode: {error};")?;
        }
        if let Some(error) = &self.declared {
            write!(f, " declared: {error};")?;
        }
        if let Some(error) = &self.transaction {
            write!(f, " transaction: {error};")?;
        }
        Ok(())
    }
}

impl std::error::Error for RestoreReport {}

/// Which aspect could not be installed, and why.
#[derive(Debug)]
#[non_exhaustive]
pub enum ApplyFailure {
    /// The thread error mode could not be installed.
    ErrorMode(ErrorModeApplyError),
    /// A declared aspect could not be installed.
    Declared(DeclaredError),
    /// The transaction could not be installed.
    Transaction(TransactionError),
    /// The impersonation context could not be applied.
    Impersonation(ImpersonationApplyError),
}

/// Applying a composite state failed, so the operation did not run.
#[derive(Debug)]
pub struct ApplyError {
    failure: ApplyFailure,
}

impl ApplyError {
    /// The underlying failure, whose variant names the aspect.
    #[must_use]
    pub const fn failure(&self) -> &ApplyFailure {
        &self.failure
    }

    /// The underlying Win32 code, if the failing aspect reported one.
    #[must_use]
    pub fn raw_os_error(&self) -> Option<i32> {
        match &self.failure {
            ApplyFailure::ErrorMode(error) => error.raw_os_error(),
            ApplyFailure::Declared(error) => error.raw_os_error(),
            ApplyFailure::Transaction(error) => error.raw_os_error(),
            ApplyFailure::Impersonation(error) => error.raw_os_error(),
        }
    }
}

impl fmt::Display for ApplyError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str("applying the ambient state failed: ")?;
        match &self.failure {
            ApplyFailure::ErrorMode(error) => write!(f, "{error}"),
            ApplyFailure::Declared(error) => write!(f, "{error}"),
            ApplyFailure::Transaction(error) => write!(f, "{error}"),
            ApplyFailure::Impersonation(error) => write!(f, "{error}"),
        }
    }
}

impl std::error::Error for ApplyError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match &self.failure {
            ApplyFailure::ErrorMode(error) => Some(error),
            ApplyFailure::Declared(error) => Some(error),
            ApplyFailure::Transaction(error) => Some(error),
            ApplyFailure::Impersonation(error) => Some(error),
        }
    }
}

#[cfg(test)]
mod tests;