Skip to main content

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;