windows_file_enumeration_sys/lib.rs
1// Copyright (c) 2026 Mike Grier
2//! Windows-only platform layer for asynchronous flat directory enumeration.
3//!
4//! One request enumerates one directory. The crate owns bounded submission and
5//! completion rings, lossless backpressure, cancellation, submitter security
6//! context transport, and a caller-buffered `GetFileInformationByHandleEx`
7//! engine. Recursive traversal belongs in a separate layer that composes these
8//! flat requests.
9//!
10//! # Native values stay native
11//!
12//! Names and paths are native-width WTF-16 ([`wtf_string`]), so an ill-formed
13//! surrogate a filesystem contains survives the round trip. Times are signed
14//! Windows tick counts ([`WindowsFileTimestamp`]), attributes are the raw
15//! `FILE_ATTRIBUTE_*` bitmask, and a file ID keeps the record's exact 16 bytes.
16//! Nothing is converted eagerly into a portable shape whose losses a caller
17//! could not undo.
18//!
19//! # Safety
20//!
21//! The public surface is entirely safe: every FFI call the native engine makes
22//! is confined to a single caller-owned, size-checked buffer
23//! ([`EnumerationRequest::with_buffer_capacity`]), and no directory entry is
24//! ever opened individually -- the engine reads only from the batched
25//! `GetFileInformationByHandleEx` listing of the one directory handle the
26//! request named. A submitted enumeration's security context is captured
27//! synchronously on the submitter's own thread, before the request becomes
28//! visible to any worker, so the later directory open always runs as whoever
29//! asked for it rather than as the pool. The unsafe internals that make this
30//! true -- buffer aliasing, handle ownership, and thread-pool callback
31//! lifetime -- are recorded in [DESIGN-NOTES.md][1] and [DESIGN-RATIONALE.md][2].
32//!
33//! [1]: https://github.com/MikeGrier/windows-threadpool-sys/blob/main/crates/windows-file-enumeration-sys/DESIGN-NOTES.md
34//! [2]: https://github.com/MikeGrier/windows-threadpool-sys/blob/main/crates/windows-file-enumeration-sys/DESIGN-RATIONALE.md
35//!
36//! # Building a predicate
37//!
38//! Build a request for one directory, delivering only files larger than 4 KiB
39//! whose names end in `.log`:
40//!
41//! ```no_run
42//! use windows_file_enumeration_sys::{
43//! ComparisonOperator, EntryType, EnumerationRequest, NamePattern, PatternToken,
44//! PredicateClause, QueryByExample,
45//! };
46//! use wtf_string::Wtf16String;
47//!
48//! let suffix = NamePattern::empty()
49//! .with(PatternToken::AnyRun)
50//! .with(PatternToken::Literal(Wtf16String::from(".log")));
51//!
52//! let query = QueryByExample::new()
53//! .with(PredicateClause::Name {
54//! pattern: suffix,
55//! case: Default::default(),
56//! negated: false,
57//! })?
58//! .with(PredicateClause::IsType {
59//! entry_type: EntryType::File,
60//! negated: false,
61//! })?
62//! .with(PredicateClause::LogicalSize {
63//! operator: ComparisonOperator::Greater,
64//! value: 4096,
65//! })?;
66//!
67//! let request = EnumerationRequest::for_path("C:/logs".as_ref())?.with_predicate(query);
68//! # Ok::<(), Box<dyn std::error::Error>>(())
69//! ```
70//!
71//! # Running an enumeration
72//!
73//! [`Session::new`] returns a producing [`Session`] and its single [`Receiver`].
74//! [`Session::try_begin`] captures the caller's own security context and starts
75//! the enumeration; entries and exactly one [`Completion::Terminal`] arrive on
76//! the receiver, every entry of one enumeration before its terminal:
77//!
78//! ```no_run
79//! use windows_file_enumeration_sys::{Completion, EnumerationRequest, Session};
80//!
81//! let (session, receiver) = Session::new(8, 8)?;
82//! let request = EnumerationRequest::for_path("C:/logs".as_ref())?;
83//! session.try_begin(request)?.detach();
84//!
85//! while let Some(completion) = receiver.recv() {
86//! match completion {
87//! Completion::Entry { entry, .. } => println!("{}", entry.name()),
88//! Completion::Terminal { outcome, .. } => {
89//! println!("finished: {outcome:?}");
90//! break;
91//! }
92//! }
93//! }
94//! # Ok::<(), Box<dyn std::error::Error>>(())
95//! ```
96//!
97//! # Traversal-style submission
98//!
99//! A recursive traversal layer captures one security context and reuses it for
100//! every directory in the tree with [`Session::try_begin_with_token`], instead
101//! of paying a fresh capture per directory on whatever thread happens to be
102//! submitting:
103//!
104//! ```no_run
105//! use windows_file_enumeration_sys::{EnumerationRequest, Session};
106//! use windows_impersonation_token_sys::ImpersonationToken;
107//!
108//! let (session, receiver) = Session::new(8, 8)?;
109//! let token = ImpersonationToken::capture()?;
110//!
111//! for directory in ["C:/logs", "C:/logs/archive"] {
112//! let request = EnumerationRequest::for_path(directory.as_ref())?;
113//! session
114//! .try_begin_with_token(request, token.clone())?
115//! .detach();
116//! }
117//! # drop(receiver);
118//! # Ok::<(), Box<dyn std::error::Error>>(())
119//! ```
120
121#![cfg(windows)]
122#![forbid(unsafe_op_in_unsafe_fn)]
123#![warn(missing_docs)]
124
125mod admission;
126mod buffer;
127mod completion;
128mod completion_ring;
129mod engine;
130mod entry;
131mod error;
132mod native;
133mod path;
134mod pattern;
135mod predicate;
136mod record;
137mod registry;
138mod request;
139mod session;
140mod submission_ring;
141mod timestamp;
142
143#[cfg(test)]
144mod model;
145#[cfg(test)]
146mod scratch;
147#[cfg(test)]
148mod testing;
149
150pub use admission::{EnumerationHandle, TokenCaptureError};
151pub use completion::{Completion, EnumerationId, TerminalOutcome};
152pub use entry::{DirectoryEntry, EntryType, FileIdentity, FileIdentityMode};
153pub use error::{
154 BeginError, BeginFailure, EnumerationError, MalformedRecord, PredicateError, PredicateFailure,
155 RequestError, RequestFailure, SessionError, SessionFailure, Win32Error,
156};
157pub use pattern::{CaseSensitivity, NamePattern, PatternToken};
158pub use predicate::{
159 ComparisonOperator, EntryPredicate, PredicateClause, QueryByExample, TimestampField,
160};
161pub use request::{DEFAULT_BUFFER_CAPACITY, EnumerationRequest, MINIMUM_BUFFER_CAPACITY};
162pub use session::{
163 MINIMUM_COMPLETION_RING_CAPACITY, MINIMUM_SUBMISSION_CAPACITY, Receiver, Session,
164};
165pub use timestamp::WindowsFileTimestamp;