windows-namespace-request-sys 0.5.0

Owned, marshalable parameter sets for synchronous Win32 namespace calls.
Documentation

windows-namespace-request-sys

Owned, marshalable parameter sets for synchronous Win32 namespace calls.

Windows only. Every public item is behind cfg(windows); the crate builds to an empty shell on other platforms.

Why

Win32's namespace and metadata surface -- opening, querying, closing -- is synchronous-only. There is no overlapped CreateFileW, no overlapped GetFileInformationByHandleEx. A call that blocks on a dead network path blocks the thread that made it, and on a shared thread that is somebody else's problem too.

This crate makes such a call capturable as a value: an owned parameter set built on one thread and executed faithfully on another. It schedules nothing -- it is the catalogue-plus-faithful-execution layer, testable with no ring, no pool, and no async anywhere near it.

What a request is, and is not

A request carries call parameters, not the thread state the call runs under. Impersonation and the rest belong to windows-thread-ambient-sys, which this crate does not depend on. The two are siblings rather than a stack: a request can be executed with no captured context at all, and a context is useful to work that never opens a file. Whoever owns both pairs them at the submission site.

A request also chooses no delivery model. An opened handle comes back plain and unassociated, because associating it with a completion port irreversibly forecloses IoRing use of it, and that choice belongs to a layer that knows the handle's destination.

Why the -sys suffix

Not in the usual sense. A -sys crate elsewhere in the ecosystem is normally raw FFI declarations with no abstraction over them, and by that reading this crate is misnamed: it declares almost no FFI of its own and takes its declarations from windows-sys.

In this workspace the suffix marks a layer, not a linking strategy: a windows-*-sys crate makes an existing Win32 API memory-safe without adding policy, and a crate that decides something Win32 has no equivalent of drops the suffix. That is why windows-waitable-queues carries no -sys -- it picks a slot protocol and an overflow policy -- while this crate does, despite wrapping a great deal of unsafe. Everything above is the suffix being earned: it schedules nothing, chooses no delivery model, and reports raw Win32 outcomes without normalising them.

The convention is stated for the whole workspace in the repository's README.

A path is copied; a handle is shared

Several entries take a handle rather than a path, and a request holds a SharedHandle: a share of the caller's handle, not a new one, and a clone of the request shares it again. Nothing in this crate duplicates a handle. The distinction is easy to get backwards: a path is a value and is copied, while a handle is a reference to a kernel object.

A request is therefore self-contained with respect to lifetime -- the handle stays open while any holder keeps its share, so a request cannot be left naming a handle its originator closed -- and not isolated with respect to state. A request names the caller's kernel object, so an enumeration through it continues the caller's rather than starting its own, and single-shot metadata queries disturb nothing. An independent traversal needs a fresh open. A caller holding only a borrow makes an owned handle itself, visibly, before handing it over.

Example

Build on the submitting thread, where a failure is still the caller's to see, then use the result on a worker that saw none of the inputs:

use std::fs;
use std::thread;

use windows_namespace_request_sys::{FileType, OpenFile, QueryFileType};
use wtf_string::Wtf16String;

let path = std::env::temp_dir().join(format!("wnrs-readme-{}.tmp", std::process::id()));
fs::write(&path, b"example")?;

// Built here rather than on the worker: a relative path is fixed to the process
// current directory now, which is shared mutable state that any thread can
// change in between. This one is absolute, so it is handed over as given.
let text = path.to_str().expect("a temporary path is valid UTF-8");
let open = OpenFile::new(Wtf16String::from(text))?;
assert_eq!(open.win32_path().to_string_lossy(), text);

// A handle is shared, not duplicated: the request holds the caller's handle, so
// it stays open for the worker however the caller lets go.
let query = QueryFileType::new(fs::File::open(&path)?);

let kind = thread::spawn(move || query.perform())
    .join()
    .expect("the worker did not panic")?;

assert_eq!(kind, FileType::DISK);
# fs::remove_file(&path)?;
# Ok::<(), Box<dyn std::error::Error>>(())

Round-one entries

One entry per Win32 call. The list is audited from three real consumers -- this repository's file watcher and enumeration crates, and MikeGrier/Globazog-rs -- rather than chosen by taste:

Win32 call Type
CreateFileW OpenFile
OpenFileById OpenFileByIdentifier
FindFirstChangeNotificationW WatchDirectory
CloseHandle and variant close routines CloseRequest
GetFileInformationByHandleEx QueryFileInformation; QueryFileIdInfo and QueryFileBasicInfo read its FileIdInfo and FileBasicInfo classes as values
GetFileInformationByHandle QueryFileInformationByHandle
GetFileType QueryFileType
GetFinalPathNameByHandleW QueryFinalPath
GetVolumeInformationByHandleW QueryVolumeInformation
GetFullPathNameW ResolveFullPath

Deletion, rename, directory creation, attribute setting, link creation, and the FindFirstFileExW family are deliberately out of round one: no audited consumer calls them. DESIGN-NOTES.md records the full list so a considered omission is distinguishable from an unexamined one.

Volume and disk operations are not here

Device controls against volumes and disks, and the virtdisk dependency query, are not namespace operations. They are in win-volume-sys, which is where this crate's storage-locality work was first built and from where it was moved.

Testing your own code against these entries

Every entry implements Request (or ConsumingRequest, for the one-shot close), so a consumer's code can be written against "a request that produces T" and exercised in that consumer's own tests with a fake -- no filesystem, no network path, no device that may or may not be present.

Two traits rather than one because the distinction is real: an open is a parameter set that may be performed repeatedly and takes &self, while a close is consumed by performing it, which is what makes closing twice impossible.

Status

The round-one catalogue is complete and its acceptance pass covers both operation coverage (every audited call site re-expressed) and scenario coverage (a request built on one thread and executed on another under a captured context, many requests across concurrent workers from one shared capture, and a handle opened by one request carried into a later one).

Design decisions are recorded in DESIGN-NOTES.md. Published on crates.io as windows-namespace-request-sys.