Skip to main content

windows_namespace_request_sys/
lib.rs

1// Copyright (c) Mike Grier.
2
3//! Owned, marshalable parameter sets for synchronous Win32 namespace calls.
4//!
5//! Win32's namespace and metadata surface -- opening, querying, closing -- is
6//! synchronous-only. A call that blocks on a dead network path blocks the thread
7//! that made it, and no overlapped form exists. This crate makes such a call
8//! **capturable as a value**: an owned parameter set built on one thread and
9//! executed faithfully on another.
10//!
11//! It schedules nothing. It is the catalogue-plus-faithful-execution layer,
12//! testable with no ring, no pool, and no async anywhere near it.
13//!
14//! # What a request is, and is not
15//!
16//! A request carries call **parameters**. It does not carry the impersonation
17//! token or any other thread-scoped state the call runs under -- that belongs to
18//! [`windows-thread-ambient-sys`][ambient], which this crate does not depend on.
19//! The two are siblings rather than a stack: a request can be executed with no
20//! captured context at all, and a context is useful to work that never opens a
21//! file. Whoever owns both pairs them at the submission site.
22//!
23//! A request also chooses **no delivery model**. An opened handle comes back
24//! plain and unassociated, because associating it with a completion port
25//! irreversibly forecloses `IoRing` use, and that choice belongs to a layer that
26//! knows the handle's destination.
27//!
28//! [ambient]: https://docs.rs/windows-thread-ambient-sys
29//!
30//! # Faithful means unaltered
31//!
32//! An entry reports the raw Win32 outcome. `ERROR_FILE_NOT_FOUND` means a
33//! missing directory from an open, an empty directory from a first query, and a
34//! genuine failure from a later one; only a consumer can tell those apart, so
35//! nothing here normalises or reclassifies.
36//!
37//! The code is also snapshotted before any cleanup can overwrite it, because
38//! `GetLastError` is volatile thread state that a `Drop` or a buffer release
39//! will happily clobber. That guarantee is a primitive rather than a rule each
40//! entry remembers: see [`outcome`].
41//!
42//! # A path is copied; a handle is duplicated
43//!
44//! Several entries take a handle rather than a path, and a request owns a
45//! **duplicate** of any handle it names. The distinction matters and is easy to
46//! get backwards: a path is a value and is copied, while a handle is a reference
47//! to a kernel object, so duplicating it *shares that object* rather than
48//! cloning it.
49//!
50//! A request is therefore self-contained with respect to **lifetime** -- it
51//! cannot be left pointing at a handle its originator closed -- and is **not**
52//! isolated with respect to **state**. Measured: a duplicated handle continues
53//! the source's directory enumeration rather than starting its own, while
54//! closing the duplicate leaves the source usable and single-shot metadata
55//! queries disturb nothing. An independent traversal needs a fresh open, not a
56//! duplicate.
57//!
58//! # Scope
59//!
60//! One entry per Win32 call; a consumer needing two makes two requests and
61//! sequences them itself. The round-one entry list is audited from three real
62//! consumers rather than chosen by taste, and its omissions are deliberate and
63//! written down. See `DESIGN-NOTES.md` in the crate root.
64//!
65//! # Example
66//!
67//! Capture the parameters on the submitting thread, where a failure is still
68//! the caller's to see and the process current directory still means what the
69//! caller thinks it means, then use them on a worker that saw none of it:
70//!
71//! ```
72//! use std::fs;
73//! use std::os::windows::io::AsHandle;
74//! use std::thread;
75//!
76//! use windows_namespace_request_sys::{CapturedHandle, prepare};
77//! use wtf_string::Wtf16String;
78//!
79//! let path = std::env::temp_dir().join(format!("wnrs-doc-{}.tmp", std::process::id()));
80//! fs::write(&path, b"example")?;
81//!
82//! // Resolved here, not on the worker: the process current directory is
83//! // shared mutable state that any thread can change in between.
84//! let text = path.to_str().expect("a temporary path is valid UTF-8");
85//! let prepared = prepare(&Wtf16String::from(text))?;
86//! assert_eq!(prepared.as_wtf16().to_string_lossy(), text);
87//!
88//! // An owned duplicate, so the captured parameters cannot be left pointing
89//! // at a handle the caller has since closed.
90//! let file = fs::File::open(&path)?;
91//! let captured = CapturedHandle::capture(file.as_handle())?;
92//! drop(file);
93//!
94//! let length = thread::spawn(move || {
95//!     fs::File::from(captured.into_owned_handle()).metadata().map(|m| m.len())
96//! })
97//! .join()
98//! .expect("the worker did not panic")?;
99//!
100//! assert_eq!(length, b"example".len() as u64);
101//! # fs::remove_file(&path)?;
102//! # Ok::<(), Box<dyn std::error::Error>>(())
103//! ```
104
105#![cfg(windows)]
106#![forbid(unsafe_op_in_unsafe_fn)]
107#![warn(missing_docs)]
108
109pub mod buffer;
110pub mod close;
111pub mod file_info;
112pub mod final_path;
113pub mod full_path;
114pub mod handle;
115pub mod open;
116pub mod open_by_id;
117pub mod outcome;
118pub mod path;
119pub mod query;
120pub mod request;
121pub mod security;
122pub mod volume;
123pub mod watch;
124
125pub use buffer::AlignedBuffer;
126pub use close::{CloseFn, CloseRequest};
127pub use file_info::QueryFileInformationByHandle;
128pub use final_path::{FinalPathError, FinalPathFlags, QueryFinalPath};
129pub use full_path::{FullPathError, ResolveFullPath};
130pub use handle::{CapturedHandle, HandleCaptureError, HandleCaptureFailure};
131pub use open::OpenFile;
132pub use open_by_id::{FileIdentifier, OpenFileByIdentifier};
133pub use outcome::{Outcome, Win32Error};
134pub use path::{PathError, PathFailure, PreparedPath, prepare};
135pub use query::{FileInformationClass, QueryFileInformation};
136pub use request::{ConsumingRequest, Request};
137/// Compiles the README's examples, so a contract change breaks the build
138/// rather than silently teaching the old answer.
139#[cfg(doctest)]
140#[doc = include_str!("../README.md")]
141struct ReadmeDoctests;
142
143#[cfg(test)]
144mod tests;
145
146pub use security::{
147    AclState, SecurityAttributes, SecurityCaptureError, SecurityCaptureFailure, SecurityDescriptor,
148};
149pub use volume::{QueryVolumeInformation, VolumeInformation};
150pub use watch::{ChangeNotification, NotifyFilter, WatchDirectory};